1 #include <ipmid/api.h>
2 #include <openssl/evp.h>
3 #include <openssl/sha.h>
4 #include <sys/mman.h>
5 #include <sys/stat.h>
6 #include <sys/types.h>
7 #include <unistd.h>
8 
9 #include <boost/algorithm/string.hpp>
10 #include <boost/container/flat_map.hpp>
11 #include <boost/process/child.hpp>
12 #include <boost/uuid/random_generator.hpp>
13 #include <boost/uuid/uuid_io.hpp>
14 #include <commandutils.hpp>
15 #include <ipmid/api.hpp>
16 #include <ipmid/utils.hpp>
17 #include <phosphor-logging/log.hpp>
18 #include <sdbusplus/bus.hpp>
19 #include <sdbusplus/bus/match.hpp>
20 #include <sdbusplus/server/object.hpp>
21 #include <sdbusplus/timer.hpp>
22 #include <types.hpp>
23 
24 #include <chrono>
25 #include <cstdint>
26 #include <filesystem>
27 #include <fstream>
28 #include <iostream>
29 #include <map>
30 #include <random>
31 #ifdef INTEL_PFR_ENABLED
32 #include <spiDev.hpp>
33 #endif
34 
35 static constexpr bool DEBUG = true;
36 static void registerFirmwareFunctions() __attribute__((constructor));
37 
38 namespace ipmi
39 {
40 namespace firmware
41 {
42 constexpr Cmd cmdGetFwVersionInfo = 0x20;
43 constexpr Cmd cmdGetFwSecurityVersionInfo = 0x21;
44 constexpr Cmd cmdGetFwUpdateChannelInfo = 0x22;
45 constexpr Cmd cmdGetBmcExecutionContext = 0x23;
46 constexpr Cmd cmdFwGetRootCertData = 0x25;
47 constexpr Cmd cmdGetFwUpdateRandomNumber = 0x26;
48 constexpr Cmd cmdSetFirmwareUpdateMode = 0x27;
49 constexpr Cmd cmdExitFirmwareUpdateMode = 0x28;
50 constexpr Cmd cmdGetSetFwUpdateControl = 0x29;
51 constexpr Cmd cmdGetFirmwareUpdateStatus = 0x2A;
52 constexpr Cmd cmdSetFirmwareUpdateOptions = 0x2B;
53 constexpr Cmd cmdFwImageWriteData = 0x2c;
54 } // namespace firmware
55 } // namespace ipmi
56 
57 namespace ipmi
58 {
59 // Custom completion codes
60 constexpr Cc ccUsbAttachOrDetachFailed = 0x80;
61 constexpr Cc ccNotSupportedInPresentState = 0xD5;
62 
63 static inline auto responseUsbAttachOrDetachFailed()
64 {
65     return response(ccUsbAttachOrDetachFailed);
66 }
67 static inline auto responseNotSupportedInPresentState()
68 {
69     return response(ccNotSupportedInPresentState);
70 }
71 } // namespace ipmi
72 
73 static constexpr const char* bmcStateIntf = "xyz.openbmc_project.State.BMC";
74 static constexpr const char* bmcStatePath = "/xyz/openbmc_project/state/bmc0";
75 static constexpr const char* bmcStateReady =
76     "xyz.openbmc_project.State.BMC.BMCState.Ready";
77 static constexpr const char* bmcStateUpdateInProgress =
78     "xyz.openbmc_project.State.BMC.BMCState.UpdateInProgress";
79 
80 static constexpr char firmwareBufferFile[] = "/tmp/fw-download.bin";
81 static std::chrono::steady_clock::time_point fwRandomNumGenTs;
82 static constexpr auto fwRandomNumExpirySeconds = std::chrono::seconds(30);
83 static constexpr size_t fwRandomNumLength = 8;
84 static std::array<uint8_t, fwRandomNumLength> fwRandomNum;
85 constexpr char usbCtrlPath[] = "/usr/bin/usb-ctrl";
86 constexpr char fwUpdateMountPoint[] = "/tmp/usb-fwupd.mnt";
87 constexpr char fwUpdateUsbVolImage[] = "/tmp/usb-fwupd.img";
88 constexpr char fwUpdateUSBDevName[] = "fw-usb-mass-storage-dev";
89 constexpr size_t fwPathMaxLength = 255;
90 
91 #ifdef INTEL_PFR_ENABLED
92 uint32_t imgLength = 0;
93 uint32_t imgType = 0;
94 bool block0Mapped = false;
95 static constexpr uint32_t perBlock0MagicNum = 0xB6EAFD19;
96 
97 static constexpr const char* bmcActivePfmMTDDev = "/dev/mtd/pfm";
98 static constexpr const char* bmcRecoveryImgMTDDev = "/dev/mtd/rc-image";
99 static constexpr size_t pfmBaseOffsetInImage = 0x400;
100 static constexpr size_t rootkeyOffsetInPfm = 0xA0;
101 static constexpr size_t cskKeyOffsetInPfm = 0x124;
102 static constexpr size_t cskSignatureOffsetInPfm = 0x19c;
103 static constexpr size_t certKeyLen = 96;
104 static constexpr size_t cskSignatureLen = 96;
105 
106 static constexpr const char* versionIntf =
107     "xyz.openbmc_project.Software.Version";
108 
109 enum class FwGetRootCertDataTag : uint8_t
110 {
111     activeRootKey = 1,
112     recoveryRootKey,
113     activeCSK,
114     recoveryCSK,
115 };
116 
117 enum class FWDeviceIDTag : uint8_t
118 {
119     bmcActiveImage = 1,
120     bmcRecoveryImage,
121 };
122 
123 const static boost::container::flat_map<FWDeviceIDTag, const char*>
124     fwVersionIdMap{{FWDeviceIDTag::bmcActiveImage,
125                     "/xyz/openbmc_project/software/bmc_active"},
126                    {FWDeviceIDTag::bmcRecoveryImage,
127                     "/xyz/openbmc_project/software/bmc_recovery"}};
128 #endif // INTEL_PFR_ENABLED
129 
130 enum class ChannelIdTag : uint8_t
131 {
132     reserved = 0,
133     kcs = 1,
134     ipmb = 2,
135     rmcpPlus = 3
136 };
137 
138 enum class BmcExecutionContext : uint8_t
139 {
140     reserved = 0,
141     linuxOs = 0x10,
142     bootLoader = 0x11,
143 };
144 
145 enum class FwUpdateCtrlReq : uint8_t
146 {
147     getCurrentControlStatus = 0x00,
148     imageTransferStart = 0x01,
149     imageTransferComplete = 0x02,
150     imageTransferAbort = 0x03,
151     setFirmwareFilename = 0x04,
152     attachUsbDevice = 0x05,
153     detachUsbDevice = 0x06
154 };
155 
156 constexpr std::size_t operator""_MB(unsigned long long v)
157 {
158     return 1024u * 1024u * v;
159 }
160 static constexpr size_t maxFirmwareImageSize = 32_MB;
161 
162 static bool localDownloadInProgress(void)
163 {
164     struct stat sb;
165     if (stat(firmwareBufferFile, &sb) < 0)
166     {
167         return false;
168     }
169     return true;
170 }
171 
172 class TransferHashCheck
173 {
174   public:
175     enum class HashCheck : uint8_t
176     {
177         notRequested = 0,
178         requested,
179         sha2Success,
180         sha2Failed = 0xe2,
181     };
182 
183   protected:
184     EVP_MD_CTX* ctx;
185     std::vector<uint8_t> expectedHash;
186     enum HashCheck check;
187     bool started;
188 
189   public:
190     TransferHashCheck() : check(HashCheck::notRequested), started(false)
191     {}
192     ~TransferHashCheck()
193     {
194         if (ctx)
195         {
196             EVP_MD_CTX_destroy(ctx);
197             ctx = NULL;
198         }
199     }
200     void init(const std::vector<uint8_t>& expected)
201     {
202         expectedHash = expected;
203         check = HashCheck::requested;
204         ctx = EVP_MD_CTX_create();
205         EVP_DigestInit(ctx, EVP_sha256());
206     }
207     void hash(const std::vector<uint8_t>& data)
208     {
209         if (!started)
210         {
211             started = true;
212         }
213         EVP_DigestUpdate(ctx, data.data(), data.size());
214     }
215     void clear()
216     {
217         // if not started, nothing to clear
218         if (started)
219         {
220             if (ctx)
221             {
222                 EVP_MD_CTX_destroy(ctx);
223             }
224             if (check != HashCheck::notRequested)
225             {
226                 check = HashCheck::requested;
227             }
228             ctx = EVP_MD_CTX_create();
229             EVP_DigestInit(ctx, EVP_sha256());
230         }
231     }
232     enum HashCheck verify()
233     {
234         if (check == HashCheck::requested)
235         {
236             unsigned int len = 0;
237             std::vector<uint8_t> digest(EVP_MD_size(EVP_sha256()));
238             EVP_DigestFinal(ctx, digest.data(), &len);
239             if (digest == expectedHash)
240             {
241                 phosphor::logging::log<phosphor::logging::level::INFO>(
242                     "Transfer sha2 verify passed.");
243                 check = HashCheck::sha2Success;
244             }
245             else
246             {
247                 phosphor::logging::log<phosphor::logging::level::ERR>(
248                     "Transfer sha2 verify failed.");
249                 check = HashCheck::sha2Failed;
250             }
251         }
252         return check;
253     }
254     uint8_t status() const
255     {
256         return static_cast<uint8_t>(check);
257     }
258 };
259 
260 class MappedFile
261 {
262   public:
263     MappedFile(const std::string& fname) : addr(nullptr), fsize(0)
264     {
265         std::error_code ec;
266         size_t sz = std::filesystem::file_size(fname, ec);
267         int fd = open(fname.c_str(), O_RDONLY);
268         if (!ec || fd < 0)
269         {
270             return;
271         }
272         void* tmp = mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0);
273         close(fd);
274         if (tmp == MAP_FAILED)
275         {
276             return;
277         }
278         addr = tmp;
279         fsize = sz;
280     }
281 
282     ~MappedFile()
283     {
284         if (addr)
285         {
286             munmap(addr, fsize);
287         }
288     }
289     const uint8_t* data() const
290     {
291         return static_cast<const uint8_t*>(addr);
292     }
293     size_t size() const
294     {
295         return fsize;
296     }
297 
298   private:
299     size_t fsize;
300     void* addr;
301 };
302 
303 class FwUpdateStatusCache
304 {
305   public:
306     enum
307     {
308         fwStateInit = 0,
309         fwStateIdle,
310         fwStateDownload,
311         fwStateVerify,
312         fwStateProgram,
313         fwStateUpdateSuccess,
314         fwStateError = 0x0f,
315         fwStateAcCycleRequired = 0x83,
316     };
317     uint8_t getState()
318     {
319         if ((fwUpdateState == fwStateIdle || fwUpdateState == fwStateInit) &&
320             localDownloadInProgress())
321         {
322             fwUpdateState = fwStateDownload;
323             progressPercent = 0;
324         }
325         return fwUpdateState;
326     }
327     void resetStatusCache()
328     {
329         unlink(firmwareBufferFile);
330     }
331     void setState(const uint8_t state)
332     {
333         switch (state)
334         {
335             case fwStateInit:
336             case fwStateIdle:
337             case fwStateError:
338                 resetStatusCache();
339                 break;
340             case fwStateDownload:
341             case fwStateVerify:
342             case fwStateProgram:
343             case fwStateUpdateSuccess:
344                 break;
345             default:
346                 // Error
347                 break;
348         }
349         fwUpdateState = state;
350     }
351     uint8_t percent()
352     {
353         return progressPercent;
354     }
355     void updateActivationPercent(const std::string& objPath)
356     {
357         std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
358         fwUpdateState = fwStateProgram;
359         progressPercent = 0;
360         match = std::make_shared<sdbusplus::bus::match::match>(
361             *busp,
362             sdbusplus::bus::match::rules::propertiesChanged(
363                 objPath, "xyz.openbmc_project.Software.ActivationProgress"),
364             [&](sdbusplus::message::message& msg) {
365                 std::map<std::string, ipmi::DbusVariant> props;
366                 std::vector<std::string> inVal;
367                 std::string iface;
368                 try
369                 {
370                     msg.read(iface, props, inVal);
371                 }
372                 catch (const std::exception& e)
373                 {
374                     phosphor::logging::log<phosphor::logging::level::ERR>(
375                         "Exception caught in get ActivationProgress");
376                     return;
377                 }
378 
379                 auto it = props.find("Progress");
380                 if (it != props.end())
381                 {
382                     progressPercent = std::get<uint8_t>(it->second);
383                     if (progressPercent == 100)
384                     {
385                         fwUpdateState = fwStateUpdateSuccess;
386                     }
387                 }
388             });
389     }
390     uint8_t activationTimerTimeout()
391     {
392         phosphor::logging::log<phosphor::logging::level::INFO>(
393             "activationTimerTimeout: Increase percentage...",
394             phosphor::logging::entry("PERCENT:%d", progressPercent));
395         progressPercent = progressPercent + 5;
396         if (progressPercent > 95)
397         {
398             /*changing the state to ready to update firmware utility */
399             fwUpdateState = fwStateUpdateSuccess;
400         }
401         return progressPercent;
402     }
403     /* API for changing state to ERROR  */
404     void firmwareUpdateAbortState()
405     {
406         unlink(firmwareBufferFile);
407         // changing the state to error
408         fwUpdateState = fwStateError;
409     }
410     void setDeferRestart(bool deferRestart)
411     {
412         deferRestartState = deferRestart;
413     }
414     void setInhibitDowngrade(bool inhibitDowngrade)
415     {
416         inhibitDowngradeState = inhibitDowngrade;
417     }
418     bool getDeferRestart()
419     {
420         return deferRestartState;
421     }
422     bool getInhibitDowngrade()
423     {
424         return inhibitDowngradeState;
425     }
426 
427   protected:
428     std::shared_ptr<sdbusplus::asio::connection> busp;
429     std::shared_ptr<sdbusplus::bus::match::match> match;
430     uint8_t fwUpdateState = 0;
431     uint8_t progressPercent = 0;
432     bool deferRestartState = false;
433     bool inhibitDowngradeState = false;
434 };
435 
436 static FwUpdateStatusCache fwUpdateStatus;
437 std::shared_ptr<TransferHashCheck> xferHashCheck;
438 
439 static void activateImage(const std::string& objPath)
440 {
441     // If flag is false  means to reboot
442     if (fwUpdateStatus.getDeferRestart() == false)
443     {
444         phosphor::logging::log<phosphor::logging::level::INFO>(
445             "activating Image: ",
446             phosphor::logging::entry("OBJPATH =%s", objPath.c_str()));
447         std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus();
448         bus->async_method_call(
449             [](const boost::system::error_code ec) {
450                 if (ec)
451                 {
452                     phosphor::logging::log<phosphor::logging::level::ERR>(
453                         "async_method_call error: activateImage failed");
454                     return;
455                 }
456             },
457             "xyz.openbmc_project.Software.BMC.Updater", objPath,
458             "org.freedesktop.DBus.Properties", "Set",
459             "xyz.openbmc_project.Software.Activation", "RequestedActivation",
460             std::variant<std::string>("xyz.openbmc_project.Software.Activation."
461                                       "RequestedActivations.Active"));
462     }
463     else
464     {
465         phosphor::logging::log<phosphor::logging::level::INFO>(
466             "Firmware image activation is deferred.");
467     }
468     fwUpdateStatus.setState(
469         static_cast<uint8_t>(FwUpdateStatusCache::fwStateUpdateSuccess));
470 }
471 
472 static bool getFirmwareUpdateMode()
473 {
474     std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
475     try
476     {
477         auto service = ipmi::getService(*busp, bmcStateIntf, bmcStatePath);
478         ipmi::Value state = ipmi::getDbusProperty(
479             *busp, service, bmcStatePath, bmcStateIntf, "CurrentBMCState");
480         std::string bmcState = std::get<std::string>(state);
481         return (bmcState == bmcStateUpdateInProgress);
482     }
483     catch (const std::exception& e)
484     {
485         phosphor::logging::log<phosphor::logging::level::ERR>(
486             "Exception caught while getting BMC state.",
487             phosphor::logging::entry("EXCEPTION=%s", e.what()));
488         throw;
489     }
490 }
491 
492 static void setFirmwareUpdateMode(const bool mode)
493 {
494 
495     std::string bmcState(bmcStateReady);
496     if (mode)
497     {
498         bmcState = bmcStateUpdateInProgress;
499     }
500 
501     std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
502     try
503     {
504         auto service = ipmi::getService(*busp, bmcStateIntf, bmcStatePath);
505         ipmi::setDbusProperty(*busp, service, bmcStatePath, bmcStateIntf,
506                               "CurrentBMCState", bmcState);
507     }
508     catch (const std::exception& e)
509     {
510         phosphor::logging::log<phosphor::logging::level::ERR>(
511             "Exception caught while setting BMC state.",
512             phosphor::logging::entry("EXCEPTION=%s", e.what()));
513         throw;
514     }
515 }
516 
517 /** @brief check if channel IPMB
518  *
519  *  This function checks if the command is from IPMB
520  *
521  * @param[in] ctx - context of current session.
522  *  @returns true if the medium is IPMB else return true.
523  **/
524 ipmi::Cc checkIPMBChannel(const ipmi::Context::ptr& ctx, bool& isIPMBChannel)
525 {
526     ipmi::ChannelInfo chInfo;
527 
528     if (ipmi::getChannelInfo(ctx->channel, chInfo) != ipmi::ccSuccess)
529     {
530         phosphor::logging::log<phosphor::logging::level::ERR>(
531             "Failed to get Channel Info",
532             phosphor::logging::entry("CHANNEL=%d", ctx->channel));
533         return ipmi::ccUnspecifiedError;
534     }
535 
536     if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) ==
537         ipmi::EChannelMediumType::ipmb)
538     {
539         isIPMBChannel = true;
540     }
541     return ipmi::ccSuccess;
542 }
543 
544 static void postTransferCompleteHandler(
545     std::unique_ptr<sdbusplus::bus::match::match>& fwUpdateMatchSignal)
546 {
547     // Setup timer for watching signal
548     static phosphor::Timer timer(
549         [&fwUpdateMatchSignal]() { fwUpdateMatchSignal = nullptr; });
550 
551     static phosphor::Timer activationStatusTimer([]() {
552         if (fwUpdateStatus.activationTimerTimeout() > 95)
553         {
554             activationStatusTimer.stop();
555             fwUpdateStatus.setState(
556                 static_cast<uint8_t>(FwUpdateStatusCache::fwStateVerify));
557         }
558     });
559 
560     timer.start(std::chrono::microseconds(5000000), false);
561 
562     // callback function for capturing signal
563     auto callback = [&](sdbusplus::message::message& m) {
564         bool flag = false;
565 
566         std::vector<std::pair<
567             std::string,
568             std::vector<std::pair<std::string, std::variant<std::string>>>>>
569             intfPropsPair;
570         sdbusplus::message::object_path objPath;
571 
572         try
573         {
574             m.read(objPath, intfPropsPair); // Read in the object path
575                                             // that was just created
576         }
577         catch (const std::exception& e)
578         {
579             phosphor::logging::log<phosphor::logging::level::ERR>(
580                 "Exception caught in reading created object path.");
581             return;
582         }
583         // constructing response message
584         phosphor::logging::log<phosphor::logging::level::INFO>(
585             "New Interface Added.",
586             phosphor::logging::entry("OBJPATH=%s", objPath.str.c_str()));
587         for (auto& interface : intfPropsPair)
588         {
589             if (interface.first == "xyz.openbmc_project.Software.Activation")
590             {
591                 // There are chances of getting two signals for
592                 // InterfacesAdded. So cross check and discrad second instance.
593                 if (fwUpdateMatchSignal == nullptr)
594                 {
595                     return;
596                 }
597                 // Found our interface, disable callbacks
598                 fwUpdateMatchSignal = nullptr;
599 
600                 phosphor::logging::log<phosphor::logging::level::INFO>(
601                     "Start activationStatusTimer for status.");
602                 try
603                 {
604                     timer.stop();
605                     activationStatusTimer.start(
606                         std::chrono::microseconds(3000000), true);
607                 }
608                 catch (const std::exception& e)
609                 {
610                     phosphor::logging::log<phosphor::logging::level::ERR>(
611                         "Exception caught in start activationStatusTimer.",
612                         phosphor::logging::entry("ERROR=%s", e.what()));
613                 }
614 
615                 fwUpdateStatus.updateActivationPercent(objPath.str);
616                 activateImage(objPath.str);
617             }
618         }
619     };
620 
621     // Adding matcher
622     fwUpdateMatchSignal = std::make_unique<sdbusplus::bus::match::match>(
623         *getSdBus(),
624         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
625         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
626         callback);
627 }
628 static bool startFirmwareUpdate(const std::string& uri)
629 {
630     // fwupdate URIs start with file:// or usb:// or tftp:// etc. By the time
631     // the code gets to this point, the file should be transferred start the
632     // request (creating a new file in /tmp/images causes the update manager to
633     // check if it is ready for activation)
634     static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatchSignal;
635     postTransferCompleteHandler(fwUpdateMatchSignal);
636     std::filesystem::rename(
637         uri, "/tmp/images/" +
638                  boost::uuids::to_string(boost::uuids::random_generator()()));
639     return true;
640 }
641 
642 static int transferImageFromFile(const std::string& uri, bool move = true)
643 {
644     std::error_code ec;
645     phosphor::logging::log<phosphor::logging::level::INFO>(
646         "Transfer Image From File.",
647         phosphor::logging::entry("URI=%s", uri.c_str()));
648     if (move)
649     {
650         std::filesystem::rename(uri, firmwareBufferFile, ec);
651     }
652     else
653     {
654         std::filesystem::copy(uri, firmwareBufferFile,
655                               std::filesystem::copy_options::overwrite_existing,
656                               ec);
657     }
658     if (xferHashCheck)
659     {
660         MappedFile mappedfw(uri);
661         xferHashCheck->hash(
662             {mappedfw.data(), mappedfw.data() + mappedfw.size()});
663     }
664     if (ec.value())
665     {
666         phosphor::logging::log<phosphor::logging::level::ERR>(
667             "Image copy failed.");
668     }
669     return ec.value();
670 }
671 
672 template <typename... ArgTypes>
673 static int executeCmd(const char* path, ArgTypes&&... tArgs)
674 {
675     boost::process::child execProg(path, const_cast<char*>(tArgs)...);
676     execProg.wait();
677     return execProg.exit_code();
678 }
679 
680 static int transferImageFromUsb(const std::string& uri)
681 {
682     int ret, sysret;
683     char fwpath[fwPathMaxLength];
684     phosphor::logging::log<phosphor::logging::level::INFO>(
685         "Transfer Image From USB.",
686         phosphor::logging::entry("URI=%s", uri.c_str()));
687     ret = executeCmd(usbCtrlPath, "mount", fwUpdateUsbVolImage,
688                      fwUpdateMountPoint);
689     if (ret)
690     {
691         return ret;
692     }
693 
694     std::string usb_path = std::string(fwUpdateMountPoint) + "/" + uri;
695     ret = transferImageFromFile(usb_path, false);
696 
697     executeCmd(usbCtrlPath, "cleanup", fwUpdateUsbVolImage, fwUpdateMountPoint);
698     return ret;
699 }
700 
701 static bool transferFirmwareFromUri(const std::string& uri)
702 {
703     static constexpr char fwUriFile[] = "file://";
704     static constexpr char fwUriUsb[] = "usb://";
705     phosphor::logging::log<phosphor::logging::level::INFO>(
706         "Transfer Image From URI.",
707         phosphor::logging::entry("URI=%s", uri.c_str()));
708     if (boost::algorithm::starts_with(uri, fwUriFile))
709     {
710         std::string fname = uri.substr(sizeof(fwUriFile) - 1);
711         if (fname != firmwareBufferFile)
712         {
713             return 0 == transferImageFromFile(fname);
714         }
715         return true;
716     }
717     if (boost::algorithm::starts_with(uri, fwUriUsb))
718     {
719         std::string fname = uri.substr(sizeof(fwUriUsb) - 1);
720         return 0 == transferImageFromUsb(fname);
721     }
722     return false;
723 }
724 
725 /* Get USB-mass-storage device status: inserted => true, ejected => false */
726 static bool getUsbStatus()
727 {
728     std::filesystem::path usbDevPath =
729         std::filesystem::path("/sys/kernel/config/usb_gadget") /
730         fwUpdateUSBDevName;
731     return (std::filesystem::exists(usbDevPath) ? true : false);
732 }
733 
734 /* Insert the USB-mass-storage device status: success => 0, failure => non-0 */
735 static int attachUsbDevice()
736 {
737     if (getUsbStatus())
738     {
739         return 1;
740     }
741     int ret = executeCmd(usbCtrlPath, "setup", fwUpdateUsbVolImage,
742                          std::to_string(maxFirmwareImageSize / 1_MB).c_str());
743     if (!ret)
744     {
745         ret = executeCmd(usbCtrlPath, "insert", fwUpdateUSBDevName,
746                          fwUpdateUsbVolImage);
747     }
748     return ret;
749 }
750 
751 /* Eject the USB-mass-storage device status: success => 0, failure => non-0 */
752 static int detachUsbDevice()
753 {
754     if (!getUsbStatus())
755     {
756         return 1;
757     }
758     return executeCmd(usbCtrlPath, "eject", fwUpdateUSBDevName);
759 }
760 static uint8_t getActiveBootImage(ipmi::Context::ptr ctx)
761 {
762     constexpr uint8_t undefinedImage = 0x00;
763     constexpr uint8_t primaryImage = 0x01;
764     constexpr uint8_t secondaryImage = 0x02;
765     constexpr const char* secondaryFitImageStartAddr = "22480000";
766 
767     uint8_t bootImage = primaryImage;
768     boost::system::error_code ec;
769     std::string value = ctx->bus->yield_method_call<std::string>(
770         ctx->yield, ec, "xyz.openbmc_project.U_Boot.Environment.Manager",
771         "/xyz/openbmc_project/u_boot/environment/mgr",
772         "xyz.openbmc_project.U_Boot.Environment.Manager", "Read", "bootcmd");
773     if (ec)
774     {
775         phosphor::logging::log<phosphor::logging::level::ERR>(
776             "Failed to read the bootcmd value");
777         /* don't fail, just give back undefined until it is ready */
778         bootImage = undefinedImage;
779     }
780 
781     /* cheking for secondary FitImage Address 22480000  */
782     else if (value.find(secondaryFitImageStartAddr) != std::string::npos)
783     {
784         bootImage = secondaryImage;
785     }
786     else
787     {
788         bootImage = primaryImage;
789     }
790 
791     return bootImage;
792 }
793 
794 #ifdef INTEL_PFR_ENABLED
795 using fwVersionInfoType = std::tuple<uint8_t,   // ID Tag
796                                      uint8_t,   // Major Version Number
797                                      uint8_t,   // Minor Version Number
798                                      uint32_t,  // Build Number
799                                      uint32_t,  // Build Timestamp
800                                      uint32_t>; // Update Timestamp
801 ipmi::RspType<uint8_t, std::vector<fwVersionInfoType>> ipmiGetFwVersionInfo()
802 {
803     // Byte 1 - Count (N) Number of devices data is being returned for.
804     // Bytes  2:16 - Device firmare information(fwVersionInfoType)
805     // Bytes - 17:(15xN) - Repeat of 2 through 16
806 
807     std::vector<fwVersionInfoType> fwVerInfoList;
808     std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus();
809     for (const auto& fwDev : fwVersionIdMap)
810     {
811         std::string verStr;
812         try
813         {
814             auto service = ipmi::getService(*busp, versionIntf, fwDev.second);
815 
816             ipmi::Value result = ipmi::getDbusProperty(
817                 *busp, service, fwDev.second, versionIntf, "Version");
818             verStr = std::get<std::string>(result);
819         }
820         catch (const std::exception& e)
821         {
822             phosphor::logging::log<phosphor::logging::level::INFO>(
823                 "Failed to fetch Version property",
824                 phosphor::logging::entry("ERROR=%s", e.what()),
825                 phosphor::logging::entry("PATH=%s", fwDev.second),
826                 phosphor::logging::entry("INTERFACE=%s", versionIntf));
827             continue;
828         }
829 
830         if (verStr.empty())
831         {
832             phosphor::logging::log<phosphor::logging::level::INFO>(
833                 "Version is empty.",
834                 phosphor::logging::entry("PATH=%s", fwDev.second),
835                 phosphor::logging::entry("INTERFACE=%s", versionIntf));
836             continue;
837         }
838 
839         // BMC Version format: <major>.<minor>-<build bum>-<build hash>
840         std::vector<std::string> splitVer;
841         boost::split(splitVer, verStr, boost::is_any_of(".-"));
842         if (splitVer.size() < 3)
843         {
844             phosphor::logging::log<phosphor::logging::level::INFO>(
845                 "Invalid Version format.",
846                 phosphor::logging::entry("Version=%s", verStr.c_str()),
847                 phosphor::logging::entry("PATH=%s", fwDev.second));
848             continue;
849         }
850 
851         uint8_t majorNum = 0;
852         uint8_t minorNum = 0;
853         uint32_t buildNum = 0;
854         try
855         {
856             majorNum = std::stoul(splitVer[0], nullptr, 16);
857             minorNum = std::stoul(splitVer[1], nullptr, 16);
858             buildNum = std::stoul(splitVer[2], nullptr, 16);
859         }
860         catch (const std::exception& e)
861         {
862             phosphor::logging::log<phosphor::logging::level::INFO>(
863                 "Failed to convert stoul.",
864                 phosphor::logging::entry("ERROR=%s", e.what()));
865             continue;
866         }
867 
868         // Build Timestamp - Not supported.
869         // Update Timestamp - TODO: Need to check with CPLD team.
870         fwVerInfoList.emplace_back(
871             fwVersionInfoType(static_cast<uint8_t>(fwDev.first), majorNum,
872                               minorNum, buildNum, 0, 0));
873     }
874 
875     return ipmi::responseSuccess(fwVerInfoList.size(), fwVerInfoList);
876 }
877 using fwSecurityVersionInfoType = std::tuple<uint8_t,  // ID Tag
878                                              uint8_t,  // BKC Version
879                                              uint8_t>; // SVN Version
880 ipmi::RspType<uint8_t, std::vector<fwSecurityVersionInfoType>>
881     ipmiGetFwSecurityVersionInfo()
882 {
883     // TODO: Need to add support.
884     return ipmi::responseInvalidCommand();
885 }
886 
887 ipmi::RspType<std::array<uint8_t, certKeyLen>,
888               std::optional<std::array<uint8_t, cskSignatureLen>>>
889     ipmiGetFwRootCertData(const ipmi::Context::ptr& ctx, uint8_t certId)
890 {
891     bool isIPMBChannel = false;
892 
893     if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess)
894     {
895         return ipmi::responseUnspecifiedError();
896     }
897     if (isIPMBChannel)
898     {
899         phosphor::logging::log<phosphor::logging::level::INFO>(
900             "Command not supported. Failed to get root certificate data.");
901         return ipmi::responseCommandNotAvailable();
902     }
903 
904     size_t certKeyOffset = 0;
905     size_t cskSigOffset = 0;
906     std::string mtdDev;
907 
908     switch (static_cast<FwGetRootCertDataTag>(certId))
909     {
910         case FwGetRootCertDataTag::activeRootKey:
911         {
912             mtdDev = bmcActivePfmMTDDev;
913             certKeyOffset = rootkeyOffsetInPfm;
914             break;
915         }
916         case FwGetRootCertDataTag::recoveryRootKey:
917         {
918             mtdDev = bmcRecoveryImgMTDDev;
919             certKeyOffset = pfmBaseOffsetInImage + rootkeyOffsetInPfm;
920             break;
921         }
922         case FwGetRootCertDataTag::activeCSK:
923         {
924             mtdDev = bmcActivePfmMTDDev;
925             certKeyOffset = cskKeyOffsetInPfm;
926             cskSigOffset = cskSignatureOffsetInPfm;
927             break;
928         }
929         case FwGetRootCertDataTag::recoveryCSK:
930         {
931             mtdDev = bmcRecoveryImgMTDDev;
932             certKeyOffset = pfmBaseOffsetInImage + cskKeyOffsetInPfm;
933             cskSigOffset = pfmBaseOffsetInImage + cskSignatureOffsetInPfm;
934             break;
935         }
936         default:
937         {
938             return ipmi::responseInvalidFieldRequest();
939         }
940     }
941 
942     std::array<uint8_t, certKeyLen> certKey = {0};
943 
944     try
945     {
946         SPIDev spiDev(mtdDev);
947         spiDev.spiReadData(certKeyOffset, certKeyLen, certKey.data());
948 
949         if (cskSigOffset)
950         {
951             std::array<uint8_t, cskSignatureLen> cskSignature = {0};
952             spiDev.spiReadData(cskSigOffset, cskSignatureLen,
953                                cskSignature.data());
954             return ipmi::responseSuccess(certKey, cskSignature);
955         }
956     }
957     catch (const std::exception& e)
958     {
959         phosphor::logging::log<phosphor::logging::level::ERR>(
960             "Exception caught in ipmiGetFwRootCertData",
961             phosphor::logging::entry("MSG=%s", e.what()));
962         return ipmi::responseUnspecifiedError();
963     }
964 
965     return ipmi::responseSuccess(certKey, std::nullopt);
966 }
967 #endif // INTEL_PFR_ENABLED
968 
969 static constexpr uint8_t channelListSize = 3;
970 /** @brief implements Maximum Firmware Transfer size command
971  *  @parameter
972  *   -  none
973  *  @returns IPMI completion code plus response data
974  *   - count - channel count
975  *   - channelList - channel list information
976  */
977 ipmi::RspType<uint8_t, // channel count
978               std::array<std::tuple<uint8_t, uint32_t>,
979                          channelListSize> // Channel List
980               >
981     ipmiFirmwareMaxTransferSize()
982 {
983     constexpr size_t kcsMaxBufSize = 128;
984     constexpr size_t rmcpPlusMaxBufSize = 50 * 1024;
985     // Byte 1 - Count (N) Number of devices data is being returned for.
986     // Byte 2 - ID Tag 00 – reserved 01 – kcs 02 – rmcp+, 03 - ipmb
987     // Byte 3-6 - transfer size (little endian)
988     // Bytes - 7:(5xN) - Repeat of 2 through 6
989     constexpr std::array<std::tuple<uint8_t, uint32_t>, channelListSize>
990         channelList = {
991             {{static_cast<uint8_t>(ChannelIdTag::kcs), kcsMaxBufSize},
992              {static_cast<uint8_t>(ChannelIdTag::rmcpPlus),
993               rmcpPlusMaxBufSize}}};
994 
995     return ipmi::responseSuccess(channelListSize, channelList);
996 }
997 
998 ipmi::RspType<uint8_t, uint8_t>
999     ipmiGetBmcExecutionContext(ipmi::Context::ptr ctx)
1000 {
1001     // Byte 1 - Current execution context
1002     //          0x10 - Linux OS, 0x11 - Bootloader, Forced-firmware updat mode
1003     // Byte 2 - Partition pointer
1004     //          0x01 - primary, 0x02 - secondary
1005     uint8_t partitionPtr = getActiveBootImage(ctx);
1006 
1007     return ipmi::responseSuccess(
1008         static_cast<uint8_t>(BmcExecutionContext::linuxOs), partitionPtr);
1009 }
1010 /** @brief Get Firmware Update Random Number
1011  *
1012  *  This function generate the random number used for
1013  *  setting the firmware update mode as authentication key.
1014  *
1015  * @param[in] ctx - context of current session
1016  *  @returns IPMI completion code along with
1017  *   - random number
1018  **/
1019 ipmi::RspType<std::array<uint8_t, fwRandomNumLength>>
1020     ipmiGetFwUpdateRandomNumber(const ipmi::Context::ptr& ctx)
1021 {
1022     phosphor::logging::log<phosphor::logging::level::INFO>(
1023         "Generate FW update random number");
1024     bool isIPMBChannel = false;
1025 
1026     if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess)
1027     {
1028         return ipmi::responseUnspecifiedError();
1029     }
1030     if (isIPMBChannel)
1031     {
1032         phosphor::logging::log<phosphor::logging::level::INFO>(
1033             "Channel not supported. Failed to fetch FW update random number");
1034         return ipmi::responseCommandNotAvailable();
1035     }
1036     std::random_device rd;
1037     std::default_random_engine gen(rd());
1038     std::uniform_int_distribution<> dist{0, 255};
1039 
1040     fwRandomNumGenTs = std::chrono::steady_clock::now();
1041 
1042     for (int i = 0; i < fwRandomNumLength; i++)
1043     {
1044         fwRandomNum[i] = dist(gen);
1045     }
1046 
1047     return ipmi::responseSuccess(fwRandomNum);
1048 }
1049 
1050 /** @brief Set Firmware Update Mode
1051  *
1052  *  This function sets BMC into firmware update mode
1053  *  after validating Random number obtained from the Get
1054  *  Firmware Update Random Number command
1055  *
1056  * @param[in] ctx - context of current session
1057  * @parameter randNum - Random number(token)
1058  * @returns IPMI completion code
1059  **/
1060 ipmi::RspType<>
1061     ipmiSetFirmwareUpdateMode(const ipmi::Context::ptr& ctx,
1062                               std::array<uint8_t, fwRandomNumLength>& randNum)
1063 {
1064     phosphor::logging::log<phosphor::logging::level::INFO>(
1065         "Start FW update mode");
1066 
1067     bool isIPMBChannel = false;
1068 
1069     if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess)
1070     {
1071         return ipmi::responseUnspecifiedError();
1072     }
1073     if (isIPMBChannel)
1074     {
1075         phosphor::logging::log<phosphor::logging::level::INFO>(
1076             "Channel not supported. Failed to set FW update mode");
1077         return ipmi::responseCommandNotAvailable();
1078     }
1079     /* Firmware Update Random number is valid for 30 seconds only */
1080     auto timeElapsed = (std::chrono::steady_clock::now() - fwRandomNumGenTs);
1081     if (std::chrono::duration_cast<std::chrono::microseconds>(timeElapsed)
1082             .count() > std::chrono::duration_cast<std::chrono::microseconds>(
1083                            fwRandomNumExpirySeconds)
1084                            .count())
1085     {
1086         phosphor::logging::log<phosphor::logging::level::INFO>(
1087             "Firmware update random number expired.");
1088         return ipmi::responseInvalidFieldRequest();
1089     }
1090 
1091     /* Validate random number */
1092     for (int i = 0; i < fwRandomNumLength; i++)
1093     {
1094         if (fwRandomNum[i] != randNum[i])
1095         {
1096             phosphor::logging::log<phosphor::logging::level::INFO>(
1097                 "Invalid random number specified.");
1098             return ipmi::responseInvalidFieldRequest();
1099         }
1100     }
1101 
1102     try
1103     {
1104         if (getFirmwareUpdateMode())
1105         {
1106             phosphor::logging::log<phosphor::logging::level::INFO>(
1107                 "Already firmware update is in progress.");
1108             return ipmi::responseBusy();
1109         }
1110     }
1111     catch (const std::exception& e)
1112     {
1113         return ipmi::responseUnspecifiedError();
1114     }
1115 
1116     // FIXME? c++ doesn't off an option for exclusive file creation
1117     FILE* fp = fopen(firmwareBufferFile, "wx");
1118     if (!fp)
1119     {
1120         phosphor::logging::log<phosphor::logging::level::INFO>(
1121             "Unable to open file.");
1122         return ipmi::responseUnspecifiedError();
1123     }
1124     fclose(fp);
1125 
1126     try
1127     {
1128         setFirmwareUpdateMode(true);
1129     }
1130     catch (const std::exception& e)
1131     {
1132         unlink(firmwareBufferFile);
1133         return ipmi::responseUnspecifiedError();
1134     }
1135 
1136     return ipmi::responseSuccess();
1137 }
1138 
1139 /** @brief implements exit firmware update mode command
1140  *  @param None
1141  *
1142  *  @returns IPMI completion code
1143  */
1144 ipmi::RspType<> ipmiExitFirmwareUpdateMode(const ipmi::Context::ptr& ctx)
1145 {
1146     phosphor::logging::log<phosphor::logging::level::INFO>(
1147         "Exit FW update mode");
1148     bool isIPMBChannel = false;
1149 
1150     if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess)
1151     {
1152         return ipmi::responseUnspecifiedError();
1153     }
1154     if (isIPMBChannel)
1155     {
1156         phosphor::logging::log<phosphor::logging::level::INFO>(
1157             "Command not supported. Failed to exit firmware update mode");
1158         return ipmi::responseCommandNotAvailable();
1159     }
1160 
1161     switch (fwUpdateStatus.getState())
1162     {
1163         case FwUpdateStatusCache::fwStateInit:
1164         case FwUpdateStatusCache::fwStateIdle:
1165             return ipmi::responseInvalidFieldRequest();
1166             break;
1167         case FwUpdateStatusCache::fwStateDownload:
1168         case FwUpdateStatusCache::fwStateVerify:
1169             break;
1170         case FwUpdateStatusCache::fwStateProgram:
1171             break;
1172         case FwUpdateStatusCache::fwStateUpdateSuccess:
1173         case FwUpdateStatusCache::fwStateError:
1174             break;
1175         case FwUpdateStatusCache::fwStateAcCycleRequired:
1176             return ipmi::responseInvalidFieldRequest();
1177             break;
1178     }
1179     fwUpdateStatus.firmwareUpdateAbortState();
1180 
1181     try
1182     {
1183         setFirmwareUpdateMode(false);
1184     }
1185     catch (const std::exception& e)
1186     {
1187         return ipmi::responseUnspecifiedError();
1188     }
1189 
1190     return ipmi::responseSuccess();
1191 }
1192 
1193 /** @brief implements Get/Set Firmware Update Control
1194  *  @param[in] ctx - context of current session
1195  *  @parameter
1196  *   - Byte 1: Control Byte
1197  *   - Byte 2: Firmware filename length (Optional)
1198  *   - Byte 3:N: Firmware filename data (Optional)
1199  *  @returns IPMI completion code plus response data
1200  *   - Byte 2: Current control status
1201  **/
1202 ipmi::RspType<bool, bool, bool, bool, uint4_t>
1203     ipmiGetSetFirmwareUpdateControl(const ipmi::Context::ptr& ctx,
1204                                     const uint8_t controlReq,
1205                                     const std::optional<std::string>& fileName)
1206 {
1207     bool isIPMBChannel = false;
1208 
1209     if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess)
1210     {
1211         return ipmi::responseUnspecifiedError();
1212     }
1213     if (isIPMBChannel)
1214     {
1215         phosphor::logging::log<phosphor::logging::level::INFO>(
1216             "Channel not supported. Failed to get or set FW update control");
1217         return ipmi::responseCommandNotAvailable();
1218     }
1219 
1220     static std::string fwXferUriPath;
1221     static bool imageTransferStarted = false;
1222     static bool imageTransferCompleted = false;
1223     static bool imageTransferAborted = false;
1224 
1225     if ((controlReq !=
1226          static_cast<uint8_t>(FwUpdateCtrlReq::setFirmwareFilename)) &&
1227         (fileName))
1228     {
1229         phosphor::logging::log<phosphor::logging::level::ERR>(
1230             "Invalid request field (Filename).");
1231         return ipmi::responseInvalidFieldRequest();
1232     }
1233 
1234     static bool usbAttached = getUsbStatus();
1235 
1236     switch (static_cast<FwUpdateCtrlReq>(controlReq))
1237     {
1238         case FwUpdateCtrlReq::getCurrentControlStatus:
1239             phosphor::logging::log<phosphor::logging::level::INFO>(
1240                 "ipmiGetSetFirmwareUpdateControl: Get status");
1241             break;
1242         case FwUpdateCtrlReq::imageTransferStart:
1243         {
1244             phosphor::logging::log<phosphor::logging::level::INFO>(
1245                 "ipmiGetSetFirmwareUpdateControl: Set transfer start");
1246             imageTransferStarted = true;
1247             // reset buffer to empty (truncate file)
1248             std::ofstream out(firmwareBufferFile,
1249                               std::ofstream::binary | std::ofstream::trunc);
1250             fwXferUriPath = std::string("file://") + firmwareBufferFile;
1251             if (xferHashCheck)
1252             {
1253                 xferHashCheck->clear();
1254             }
1255             // Setting state to download
1256             fwUpdateStatus.setState(
1257                 static_cast<uint8_t>(FwUpdateStatusCache::fwStateDownload));
1258 #ifdef INTEL_PFR_ENABLED
1259             imgLength = 0;
1260             imgType = 0;
1261             block0Mapped = false;
1262 #endif
1263         }
1264         break;
1265         case FwUpdateCtrlReq::imageTransferComplete:
1266         {
1267             phosphor::logging::log<phosphor::logging::level::INFO>(
1268                 "ipmiGetSetFirmwareUpdateControl: Set transfer complete.");
1269             if (usbAttached)
1270             {
1271                 phosphor::logging::log<phosphor::logging::level::ERR>(
1272                     "USB should be detached to perform this operation.");
1273                 return ipmi::responseNotSupportedInPresentState();
1274             }
1275             // finish transfer based on URI
1276             if (!transferFirmwareFromUri(fwXferUriPath))
1277             {
1278                 phosphor::logging::log<phosphor::logging::level::ERR>(
1279                     "transferFirmwareFromUri failed.");
1280                 return ipmi::responseUnspecifiedError();
1281             }
1282             // transfer complete
1283             if (xferHashCheck)
1284             {
1285                 if (TransferHashCheck::HashCheck::sha2Success !=
1286                     xferHashCheck->verify())
1287                 {
1288                     phosphor::logging::log<phosphor::logging::level::ERR>(
1289                         "xferHashCheck failed.");
1290                     return ipmi::responseUnspecifiedError();
1291                 }
1292             }
1293             // Set state to verify and start the update
1294             fwUpdateStatus.setState(
1295                 static_cast<uint8_t>(FwUpdateStatusCache::fwStateVerify));
1296             // start the request
1297             if (!startFirmwareUpdate(firmwareBufferFile))
1298             {
1299                 phosphor::logging::log<phosphor::logging::level::ERR>(
1300                     "startFirmwareUpdate failed.");
1301                 return ipmi::responseUnspecifiedError();
1302             }
1303             imageTransferCompleted = true;
1304         }
1305         break;
1306         case FwUpdateCtrlReq::imageTransferAbort:
1307             phosphor::logging::log<phosphor::logging::level::INFO>(
1308                 "ipmiGetSetFirmwareUpdateControl: Set transfer abort.");
1309             if (usbAttached)
1310             {
1311                 if (detachUsbDevice())
1312                 {
1313                     phosphor::logging::log<phosphor::logging::level::ERR>(
1314                         "Detach USB device failed.");
1315                     return ipmi::responseUsbAttachOrDetachFailed();
1316                 }
1317                 usbAttached = false;
1318             }
1319             // During abort request reset the state to Init by cleaning update
1320             // file.
1321             fwUpdateStatus.firmwareUpdateAbortState();
1322             imageTransferAborted = true;
1323             break;
1324         case FwUpdateCtrlReq::setFirmwareFilename:
1325             phosphor::logging::log<phosphor::logging::level::INFO>(
1326                 "ipmiGetSetFirmwareUpdateControl: Set filename.");
1327             if (!fileName || ((*fileName).length() == 0))
1328             {
1329                 phosphor::logging::log<phosphor::logging::level::ERR>(
1330                     "Invalid Filename specified.");
1331                 return ipmi::responseInvalidFieldRequest();
1332             }
1333 
1334             fwXferUriPath = *fileName;
1335             break;
1336         case FwUpdateCtrlReq::attachUsbDevice:
1337             phosphor::logging::log<phosphor::logging::level::INFO>(
1338                 "ipmiGetSetFirmwareUpdateControl: Attach USB device.");
1339             if (usbAttached)
1340             {
1341                 phosphor::logging::log<phosphor::logging::level::ERR>(
1342                     "USB device is already attached.");
1343                 return ipmi::responseInvalidFieldRequest();
1344             }
1345             if (attachUsbDevice())
1346             {
1347                 phosphor::logging::log<phosphor::logging::level::ERR>(
1348                     "Attach USB device failed.");
1349                 return ipmi::responseUsbAttachOrDetachFailed();
1350             }
1351             usbAttached = true;
1352             break;
1353         case FwUpdateCtrlReq::detachUsbDevice:
1354             phosphor::logging::log<phosphor::logging::level::INFO>(
1355                 "ipmiGetSetFirmwareUpdateControl: Detach USB device.");
1356             if (!usbAttached)
1357             {
1358                 phosphor::logging::log<phosphor::logging::level::ERR>(
1359                     "USB device is not attached.");
1360                 return ipmi::responseInvalidFieldRequest();
1361             }
1362             if (detachUsbDevice())
1363             {
1364                 phosphor::logging::log<phosphor::logging::level::ERR>(
1365                     "Detach USB device failed.");
1366                 return ipmi::responseUsbAttachOrDetachFailed();
1367             }
1368             usbAttached = false;
1369             break;
1370         default:
1371             phosphor::logging::log<phosphor::logging::level::ERR>(
1372                 "Invalid control option specified.");
1373             return ipmi::responseInvalidFieldRequest();
1374     }
1375 
1376     return ipmi::responseSuccess(imageTransferStarted, imageTransferCompleted,
1377                                  imageTransferAborted, usbAttached, uint4_t(0));
1378 }
1379 
1380 /** @brief implements firmware get status command
1381  *  @parameter
1382  *   -  none
1383  *  @returns IPMI completion code plus response data
1384  *   - status     -  processing status
1385  *   - percentage -  percentage completion
1386  *   - check      -  channel integrity check status
1387  **/
1388 ipmi::RspType<uint8_t, // status
1389               uint8_t, // percentage
1390               uint8_t  // check
1391               >
1392     ipmiGetFirmwareUpdateStatus()
1393 
1394 {
1395     // Byte 1 - status (0=init, 1=idle, 2=download, 3=validate, 4=write,
1396     //                  5=ready, f=error, 83=ac cycle required)
1397     // Byte 2 - percent
1398     // Byte 3 - integrity check status (0=none, 1=req, 2=sha2ok, e2=sha2fail)
1399     uint8_t status = fwUpdateStatus.getState();
1400     uint8_t percent = fwUpdateStatus.percent();
1401     uint8_t check = xferHashCheck ? xferHashCheck->status() : 0;
1402 
1403     // Status code.
1404     return ipmi::responseSuccess(status, percent, check);
1405 }
1406 
1407 ipmi::RspType<bool, bool, bool, uint5_t> ipmiSetFirmwareUpdateOptions(
1408     const ipmi::Context::ptr& ctx, bool noDowngradeMask, bool deferRestartMask,
1409     bool sha2CheckMask, uint5_t reserved1, bool noDowngrade, bool deferRestart,
1410     bool sha2Check, uint5_t reserved2,
1411     std::optional<std::vector<uint8_t>> integrityCheckVal)
1412 {
1413     phosphor::logging::log<phosphor::logging::level::INFO>(
1414         "Set firmware update options.");
1415     bool isIPMBChannel = false;
1416 
1417     if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess)
1418     {
1419         return ipmi::responseUnspecifiedError();
1420     }
1421     if (isIPMBChannel)
1422     {
1423         phosphor::logging::log<phosphor::logging::level::INFO>(
1424             "Channel not supported. Failed to set firmware update options");
1425         return ipmi::responseCommandNotAvailable();
1426     }
1427     bool noDowngradeState = fwUpdateStatus.getInhibitDowngrade();
1428     bool deferRestartState = fwUpdateStatus.getDeferRestart();
1429     bool sha2CheckState = xferHashCheck ? true : false;
1430 
1431     if (noDowngradeMask && (noDowngradeState != noDowngrade))
1432     {
1433         fwUpdateStatus.setInhibitDowngrade(noDowngrade);
1434         noDowngradeState = noDowngrade;
1435     }
1436     if (deferRestartMask && (deferRestartState != deferRestart))
1437     {
1438         fwUpdateStatus.setDeferRestart(deferRestart);
1439         deferRestartState = deferRestart;
1440     }
1441     if (sha2CheckMask)
1442     {
1443         if (sha2Check)
1444         {
1445             auto hashSize = EVP_MD_size(EVP_sha256());
1446             if ((*integrityCheckVal).size() != hashSize)
1447             {
1448                 phosphor::logging::log<phosphor::logging::level::DEBUG>(
1449                     "Invalid size of Hash specified.");
1450                 return ipmi::responseInvalidFieldRequest();
1451             }
1452             xferHashCheck = std::make_shared<TransferHashCheck>();
1453             xferHashCheck->init(*integrityCheckVal);
1454         }
1455         else
1456         {
1457             // delete the xferHashCheck object
1458             xferHashCheck.reset();
1459         }
1460         sha2CheckState = sha2CheckMask;
1461     }
1462     return ipmi::responseSuccess(noDowngradeState, deferRestartState,
1463                                  sha2CheckState, reserved1);
1464 }
1465 
1466 ipmi::RspType<uint32_t>
1467     ipmiFwImageWriteData(const std::vector<uint8_t>& writeData)
1468 {
1469     const uint8_t ccCmdNotSupportedInPresentState = 0xD5;
1470     size_t writeDataLen = writeData.size();
1471 
1472     if (!writeDataLen)
1473     {
1474         return ipmi::responseReqDataLenInvalid();
1475     }
1476 
1477     if (fwUpdateStatus.getState() != FwUpdateStatusCache::fwStateDownload)
1478     {
1479         phosphor::logging::log<phosphor::logging::level::ERR>(
1480             "Invalid firmware update state.");
1481         return ipmi::response(ccCmdNotSupportedInPresentState);
1482     }
1483 
1484     std::ofstream out(firmwareBufferFile,
1485                       std::ofstream::binary | std::ofstream::app);
1486     if (!out)
1487     {
1488         phosphor::logging::log<phosphor::logging::level::DEBUG>(
1489             "Error while opening file.");
1490         return ipmi::responseUnspecifiedError();
1491     }
1492 
1493     uint64_t fileDataLen = out.tellp();
1494 
1495     if ((fileDataLen + writeDataLen) > maxFirmwareImageSize)
1496     {
1497         phosphor::logging::log<phosphor::logging::level::DEBUG>(
1498             "Firmware image size exceeds the limit");
1499         return ipmi::responseInvalidFieldRequest();
1500     }
1501 
1502     const char* data = reinterpret_cast<const char*>(writeData.data());
1503     out.write(data, writeDataLen);
1504     out.close();
1505 
1506     if (xferHashCheck)
1507     {
1508         xferHashCheck->hash(writeData);
1509     }
1510 
1511 #ifdef INTEL_PFR_ENABLED
1512     /* PFR image block 0 - As defined in HAS */
1513     struct PFRImageBlock0
1514     {
1515         uint32_t tag;
1516         uint32_t pcLength;
1517         uint32_t pcType;
1518         uint32_t reserved1;
1519         uint8_t hash256[32];
1520         uint8_t hash384[48];
1521         uint8_t reserved2[32];
1522     } __attribute__((packed));
1523 
1524     /* Get the PFR block 0 data and read the uploaded image
1525      * information( Image type, length etc) */
1526     if (((fileDataLen + writeDataLen) >= sizeof(PFRImageBlock0)) &&
1527         (!block0Mapped))
1528     {
1529         struct PFRImageBlock0 block0Data = {0};
1530 
1531         std::ifstream inFile(firmwareBufferFile,
1532                              std::ios::binary | std::ios::in);
1533         inFile.read(reinterpret_cast<char*>(&block0Data), sizeof(block0Data));
1534         inFile.close();
1535 
1536         uint32_t magicNum = block0Data.tag;
1537 
1538         /* Validate the magic number */
1539         if (magicNum != perBlock0MagicNum)
1540         {
1541             phosphor::logging::log<phosphor::logging::level::DEBUG>(
1542                 "PFR image magic number not matched");
1543             return ipmi::responseInvalidFieldRequest();
1544         }
1545         // Note:imgLength, imgType and block0Mapped are in global scope, as
1546         // these are used in cascaded updates.
1547         imgLength = block0Data.pcLength;
1548         imgType = block0Data.pcType;
1549         block0Mapped = true;
1550     }
1551 #endif // end of INTEL_PFR_ENABLED
1552     return ipmi::responseSuccess(writeDataLen);
1553 }
1554 
1555 static void registerFirmwareFunctions()
1556 {
1557     // guarantee that we start with an already timed out timestamp
1558     fwRandomNumGenTs =
1559         std::chrono::steady_clock::now() - fwRandomNumExpirySeconds;
1560     fwUpdateStatus.setState(
1561         static_cast<uint8_t>(FwUpdateStatusCache::fwStateInit));
1562 
1563     unlink(firmwareBufferFile);
1564 
1565 #ifdef INTEL_PFR_ENABLED
1566     // Following commands are supported only for PFR enabled platforms
1567     // CMD:0x20 - Get Firmware Version Information
1568     // CMD:0x21 - Get Firmware Security Version Information
1569     // CMD:0x25 - Get Root Certificate Data
1570 
1571     // get firmware version information
1572     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnFirmware,
1573                           ipmi::firmware::cmdGetFwVersionInfo,
1574                           ipmi::Privilege::Admin, ipmiGetFwVersionInfo);
1575 
1576     // get firmware security version information
1577     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnFirmware,
1578                           ipmi::firmware::cmdGetFwSecurityVersionInfo,
1579                           ipmi::Privilege::Admin, ipmiGetFwSecurityVersionInfo);
1580 
1581     // get root certificate data
1582     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnFirmware,
1583                           ipmi::firmware::cmdFwGetRootCertData,
1584                           ipmi::Privilege::Admin, ipmiGetFwRootCertData);
1585 #endif
1586 
1587     // get firmware update channel information (max transfer sizes)
1588     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware,
1589                           ipmi::firmware::cmdGetFwUpdateChannelInfo,
1590                           ipmi::Privilege::Admin, ipmiFirmwareMaxTransferSize);
1591 
1592     // get bmc execution context
1593     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware,
1594                           ipmi::firmware::cmdGetBmcExecutionContext,
1595                           ipmi::Privilege::Admin, ipmiGetBmcExecutionContext);
1596 
1597     // Get Firmware Update Random number
1598     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware,
1599                           ipmi::firmware::cmdGetFwUpdateRandomNumber,
1600                           ipmi::Privilege::Admin, ipmiGetFwUpdateRandomNumber);
1601 
1602     // Set Firmware Update Mode
1603     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware,
1604                           ipmi::firmware::cmdSetFirmwareUpdateMode,
1605                           ipmi::Privilege::Admin, ipmiSetFirmwareUpdateMode);
1606 
1607     // Exit Firmware Update Mode
1608     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware,
1609                           ipmi::firmware::cmdExitFirmwareUpdateMode,
1610                           ipmi::Privilege::Admin, ipmiExitFirmwareUpdateMode);
1611 
1612     // Get/Set Firmware Update Control
1613     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware,
1614                           ipmi::firmware::cmdGetSetFwUpdateControl,
1615                           ipmi::Privilege::Admin,
1616                           ipmiGetSetFirmwareUpdateControl);
1617 
1618     // Get Firmware Update Status
1619     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware,
1620                           ipmi::firmware::cmdGetFirmwareUpdateStatus,
1621                           ipmi::Privilege::Admin, ipmiGetFirmwareUpdateStatus);
1622 
1623     // Set Firmware Update Options
1624     ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware,
1625                           ipmi::firmware::cmdSetFirmwareUpdateOptions,
1626                           ipmi::Privilege::Admin, ipmiSetFirmwareUpdateOptions);
1627     // write image data
1628     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnFirmware,
1629                           ipmi::firmware::cmdFwImageWriteData,
1630                           ipmi::Privilege::Admin, ipmiFwImageWriteData);
1631     return;
1632 }
1633