xref: /openbmc/estoraged/src/estoraged.cpp (revision da5aa614090fbd22399db85ec8866353c4c22d9c)
1 
2 #include "estoraged.hpp"
3 
4 #include "cryptErase.hpp"
5 #include "cryptsetupInterface.hpp"
6 #include "pattern.hpp"
7 #include "sanitize.hpp"
8 #include "verifyDriveGeometry.hpp"
9 #include "zero.hpp"
10 
11 #include <libcryptsetup.h>
12 #include <linux/mmc/core.h>
13 #include <linux/mmc/ioctl.h>
14 #include <linux/mmc/mmc.h>
15 #include <openssl/rand.h>
16 #include <sys/ioctl.h>
17 
18 #include <phosphor-logging/lg2.hpp>
19 #include <sdbusplus/asio/object_server.hpp>
20 #include <stdplus/fd/create.hpp>
21 #include <stdplus/fd/managed.hpp>
22 #include <xyz/openbmc_project/Common/error.hpp>
23 
24 #include <cstdlib>
25 #include <filesystem>
26 #include <iostream>
27 #include <string>
28 #include <string_view>
29 #include <utility>
30 #include <vector>
31 
32 namespace estoraged
33 {
34 
35 using Association = std::tuple<std::string, std::string, std::string>;
36 using sdbusplus::asio::PropertyPermission;
37 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
38 using sdbusplus::xyz::openbmc_project::Common::Error::UnsupportedRequest;
39 using sdbusplus::xyz::openbmc_project::Inventory::Item::server::Drive;
40 using sdbusplus::xyz::openbmc_project::Inventory::Item::server::Volume;
41 
EStoraged(std::unique_ptr<stdplus::Fd> fd,sdbusplus::asio::object_server & server,const std::string & configPath,const std::string & devPath,const std::string & luksName,uint64_t size,uint8_t lifeTime,const std::string & partNumber,const std::string & serialNumber,const std::string & locationCode,uint64_t eraseMaxGeometry,uint64_t eraseMinGeometry,const std::string & driveType,const std::string & driveProtocol,std::unique_ptr<CryptsetupInterface> cryptInterface,std::unique_ptr<FilesystemInterface> fsInterface)42 EStoraged::EStoraged(
43     std::unique_ptr<stdplus::Fd> fd, sdbusplus::asio::object_server& server,
44     const std::string& configPath, const std::string& devPath,
45     const std::string& luksName, uint64_t size, uint8_t lifeTime,
46     const std::string& partNumber, const std::string& serialNumber,
47     const std::string& locationCode, uint64_t eraseMaxGeometry,
48     uint64_t eraseMinGeometry, const std::string& driveType,
49     const std::string& driveProtocol,
50     std::unique_ptr<CryptsetupInterface> cryptInterface,
51     std::unique_ptr<FilesystemInterface> fsInterface) :
52     devPath(devPath), containerName(luksName),
53     mountPoint("/mnt/" + luksName + "_fs"), eraseMaxGeometry(eraseMaxGeometry),
54     eraseMinGeometry(eraseMinGeometry), cryptIface(std::move(cryptInterface)),
55     fsIface(std::move(fsInterface)),
56     cryptDevicePath(cryptIface->cryptGetDir() + "/" + luksName),
57     objectServer(server)
58 {
59     try
60     {
61         enableBackgroundOperation(std::move(fd), devPath);
62     }
63     catch (const BkopsError& e)
64     {
65         lg2::error("Failed to enable background operation for {PATH}: {ERROR}",
66                    "PATH", devPath, "ERROR", e.what());
67     }
68 
69     /* Get the filename of the device (without "/dev/"). */
70     std::string deviceName = std::filesystem::path(devPath).filename().string();
71     /* DBus object path */
72     std::string objectPath =
73         "/xyz/openbmc_project/inventory/storage/" + deviceName;
74 
75     /* Add Volume interface. */
76     volumeInterface = objectServer.add_interface(
77         objectPath, "xyz.openbmc_project.Inventory.Item.Volume");
78     volumeInterface->register_method(
79         "FormatLuks", [this](const std::vector<uint8_t>& password,
80                              Volume::FilesystemType type) {
81             this->formatLuks(password, type);
82         });
83     volumeInterface->register_method(
84         "Erase",
85         [this](Volume::EraseMethod eraseType) { this->erase(eraseType); });
86     volumeInterface->register_method("Lock", [this]() { this->lock(); });
87     volumeInterface->register_method(
88         "Unlock",
89         [this](std::vector<uint8_t>& password) { this->unlock(password); });
90     volumeInterface->register_method(
91         "ChangePassword", [this](const std::vector<uint8_t>& oldPassword,
92                                  const std::vector<uint8_t>& newPassword) {
93             this->changePassword(oldPassword, newPassword);
94         });
95     volumeInterface->register_property_r(
96         "Locked", lockedProperty, sdbusplus::vtable::property_::emits_change,
97         [this](bool& value) {
98             value = this->isLocked();
99             return value;
100         });
101 
102     /* Add Drive interface. */
103     driveInterface = objectServer.add_interface(
104         objectPath, "xyz.openbmc_project.Inventory.Item.Drive");
105     driveInterface->register_property("Capacity", size);
106     /* The lifetime property is read/write only for testing purposes. */
107     driveInterface->register_property("PredictedMediaLifeLeftPercent", lifeTime,
108                                       PropertyPermission::readWrite);
109     driveInterface->register_property(
110         "Type",
111         "xyz.openbmc_project.Inventory.Item.Drive.DriveType." + driveType);
112     driveInterface->register_property(
113         "Protocol", "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol." +
114                         driveProtocol);
115     /* This registers the Locked property for the Drives interface.
116      * Now it is the same as the volume Locked property */
117     driveInterface->register_property_r(
118         "Locked", lockedProperty, sdbusplus::vtable::property_::emits_change,
119         [this](bool& value) {
120             value = this->isLocked();
121             return value;
122         });
123 
124     driveInterface->register_property_r(
125         "EncryptionStatus", encryptionStatus,
126         sdbusplus::vtable::property_::emits_change,
127         [this](Drive::DriveEncryptionState& value) {
128             value = this->findEncryptionStatus();
129             return value;
130         });
131 
132     embeddedLocationInterface = objectServer.add_interface(
133         objectPath, "xyz.openbmc_project.Inventory.Connector.Embedded");
134 
135     if (!locationCode.empty())
136     {
137         locationCodeInterface = objectServer.add_interface(
138             objectPath, "xyz.openbmc_project.Inventory.Decorator.LocationCode");
139         locationCodeInterface->register_property("LocationCode", locationCode);
140         locationCodeInterface->initialize();
141     }
142 
143     /* Add Asset interface. */
144     assetInterface = objectServer.add_interface(
145         objectPath, "xyz.openbmc_project.Inventory.Decorator.Asset");
146     assetInterface->register_property("PartNumber", partNumber);
147     assetInterface->register_property("SerialNumber", serialNumber);
148 
149     volumeInterface->initialize();
150     driveInterface->initialize();
151     embeddedLocationInterface->initialize();
152     assetInterface->initialize();
153 
154     /* Set up the association between chassis and drive. */
155     association = objectServer.add_interface(
156         objectPath, "xyz.openbmc_project.Association.Definitions");
157 
158     std::vector<Association> associations;
159     associations.emplace_back("chassis", "drive",
160                               std::filesystem::path(configPath).parent_path());
161     association->register_property("Associations", associations);
162     association->initialize();
163 }
164 
~EStoraged()165 EStoraged::~EStoraged()
166 {
167     objectServer.remove_interface(volumeInterface);
168     objectServer.remove_interface(driveInterface);
169     objectServer.remove_interface(embeddedLocationInterface);
170     objectServer.remove_interface(assetInterface);
171     objectServer.remove_interface(association);
172 
173     if (locationCodeInterface != nullptr)
174     {
175         objectServer.remove_interface(locationCodeInterface);
176     }
177 }
178 
formatLuks(const std::vector<uint8_t> & password,Volume::FilesystemType type)179 void EStoraged::formatLuks(const std::vector<uint8_t>& password,
180                            Volume::FilesystemType type)
181 {
182     std::string msg = "OpenBMC.0.1.DriveFormat";
183     lg2::info("Starting format", "REDFISH_MESSAGE_ID", msg);
184 
185     if (type != Volume::FilesystemType::ext4)
186     {
187         lg2::error("Only ext4 filesystems are supported currently",
188                    "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.FormatFail"));
189         throw UnsupportedRequest();
190     }
191 
192     formatLuksDev(password);
193     activateLuksDev(password);
194 
195     createFilesystem();
196     mountFilesystem();
197 }
198 
erase(Volume::EraseMethod inEraseMethod)199 void EStoraged::erase(Volume::EraseMethod inEraseMethod)
200 {
201     std::cerr << "Erasing encrypted eMMC" << std::endl;
202     lg2::info("Starting erase", "REDFISH_MESSAGE_ID",
203               std::string("OpenBMC.0.1.DriveErase"));
204     switch (inEraseMethod)
205     {
206         case Volume::EraseMethod::CryptoErase:
207         {
208             CryptErase myCryptErase(devPath);
209             myCryptErase.doErase();
210             break;
211         }
212         case Volume::EraseMethod::VerifyGeometry:
213         {
214             VerifyDriveGeometry myVerifyGeometry(devPath);
215             myVerifyGeometry.geometryOkay(eraseMaxGeometry, eraseMinGeometry);
216             break;
217         }
218         case Volume::EraseMethod::LogicalOverWrite:
219         {
220             Pattern myErasePattern(devPath);
221             myErasePattern.writePattern();
222             break;
223         }
224         case Volume::EraseMethod::LogicalVerify:
225         {
226             Pattern myErasePattern(devPath);
227             myErasePattern.verifyPattern();
228             break;
229         }
230         case Volume::EraseMethod::VendorSanitize:
231         {
232             Sanitize mySanitize(devPath);
233             mySanitize.doSanitize();
234             break;
235         }
236         case Volume::EraseMethod::ZeroOverWrite:
237         {
238             Zero myZero(devPath);
239             myZero.writeZero();
240             break;
241         }
242         case Volume::EraseMethod::ZeroVerify:
243         {
244             Zero myZero(devPath);
245             myZero.verifyZero();
246             break;
247         }
248         case Volume::EraseMethod::SecuredLocked:
249         {
250             if (!isLocked())
251             {
252                 lock();
253             }
254             // TODO: implement hardware locking
255             // Until that is done, we can lock using eStoraged::lock()
256             break;
257         }
258     }
259 }
260 
lock()261 void EStoraged::lock()
262 {
263     std::string msg = "OpenBMC.0.1.DriveLock";
264     lg2::info("Starting lock", "REDFISH_MESSAGE_ID", msg);
265 
266     unmountFilesystem();
267     deactivateLuksDev();
268 }
269 
unlock(std::vector<uint8_t> password)270 void EStoraged::unlock(std::vector<uint8_t> password)
271 {
272     std::string msg = "OpenBMC.0.1.DriveUnlock";
273     lg2::info("Starting unlock", "REDFISH_MESSAGE_ID", msg);
274 
275     activateLuksDev(std::move(password));
276     mountFilesystem();
277 }
278 
changePassword(const std::vector<uint8_t> & oldPassword,const std::vector<uint8_t> & newPassword)279 void EStoraged::changePassword(const std::vector<uint8_t>& oldPassword,
280                                const std::vector<uint8_t>& newPassword)
281 {
282     lg2::info("Starting change password", "REDFISH_MESSAGE_ID",
283               std::string("OpenBMC.0.1.DrivePasswordChanged"));
284 
285     CryptHandle cryptHandle = loadLuksHeader();
286 
287     int retval = cryptIface->cryptKeyslotChangeByPassphrase(
288         cryptHandle.get(), CRYPT_ANY_SLOT, CRYPT_ANY_SLOT,
289         reinterpret_cast<const char*>(oldPassword.data()), oldPassword.size(),
290         reinterpret_cast<const char*>(newPassword.data()), newPassword.size());
291     if (retval < 0)
292     {
293         lg2::error("Failed to change password", "REDFISH_MESSAGE_ID",
294                    std::string("OpenBMC.0.1.DrivePasswordChangeFail"));
295         throw InternalFailure();
296     }
297 
298     lg2::info("Successfully changed password for {DEV}", "DEV", devPath,
299               "REDFISH_MESSAGE_ID",
300               std::string("OpenBMC.0.1.DrivePasswordChangeSuccess"));
301 }
302 
isLocked() const303 bool EStoraged::isLocked() const
304 {
305     /*
306      * Check if the mapped virtual device exists. If it exists, the LUKS volume
307      * is unlocked.
308      */
309     try
310     {
311         std::filesystem::path mappedDevicePath(cryptDevicePath);
312         return (std::filesystem::exists(mappedDevicePath) == false);
313     }
314     catch (const std::exception& e)
315     {
316         lg2::error("Failed to query locked status: {EXCEPT}", "EXCEPT",
317                    e.what(), "REDFISH_MESSAGE_ID",
318                    std::string("OpenBMC.0.1.IsLockedFail"));
319         /* If we couldn't query the filesystem path, assume unlocked. */
320         return false;
321     }
322 }
323 
getMountPoint() const324 std::string_view EStoraged::getMountPoint() const
325 {
326     return mountPoint;
327 }
328 
formatLuksDev(std::vector<uint8_t> password)329 void EStoraged::formatLuksDev(std::vector<uint8_t> password)
330 {
331     lg2::info("Formatting device {DEV}", "DEV", devPath, "REDFISH_MESSAGE_ID",
332               std::string("OpenBMC.0.1.FormatLuksDev"));
333 
334     /* Generate the volume key. */
335     const std::size_t keySize = 64;
336     std::vector<uint8_t> volumeKey(keySize);
337     if (RAND_bytes(volumeKey.data(), keySize) != 1)
338     {
339         lg2::error("Failed to create volume key", "REDFISH_MESSAGE_ID",
340                    std::string("OpenBMC.0.1.FormatLuksDevFail"));
341         throw InternalFailure();
342     }
343 
344     /* Create the handle. */
345     CryptHandle cryptHandle(devPath);
346 
347     /* Format the LUKS encrypted device. */
348     int retval = cryptIface->cryptFormat(
349         cryptHandle.get(), CRYPT_LUKS2, "aes", "xts-plain64", nullptr,
350         reinterpret_cast<const char*>(volumeKey.data()), volumeKey.size(),
351         nullptr);
352     if (retval < 0)
353     {
354         lg2::error("Failed to format encrypted device: {RETVAL}", "RETVAL",
355                    retval, "REDFISH_MESSAGE_ID",
356                    std::string("OpenBMC.0.1.FormatLuksDevFail"));
357         throw InternalFailure();
358     }
359 
360     /* Set the password. */
361     retval = cryptIface->cryptKeyslotAddByVolumeKey(
362         cryptHandle.get(), CRYPT_ANY_SLOT, nullptr, 0,
363         reinterpret_cast<const char*>(password.data()), password.size());
364 
365     if (retval < 0)
366     {
367         lg2::error("Failed to set encryption password", "REDFISH_MESSAGE_ID",
368                    std::string("OpenBMC.0.1.FormatLuksDevFail"));
369         throw InternalFailure();
370     }
371 
372     lg2::info("Encrypted device {DEV} successfully formatted", "DEV", devPath,
373               "REDFISH_MESSAGE_ID",
374               std::string("OpenBMC.0.1.FormatLuksDevSuccess"));
375 }
376 
loadLuksHeader()377 CryptHandle EStoraged::loadLuksHeader()
378 {
379     CryptHandle cryptHandle(devPath);
380 
381     int retval = cryptIface->cryptLoad(cryptHandle.get(), CRYPT_LUKS2, nullptr);
382     if (retval < 0)
383     {
384         lg2::error("Failed to load LUKS header: {RETVAL}", "RETVAL", retval,
385                    "REDFISH_MESSAGE_ID",
386                    std::string("OpenBMC.0.1.ActivateLuksDevFail"));
387         throw InternalFailure();
388     }
389     return cryptHandle;
390 }
391 
findEncryptionStatus()392 Drive::DriveEncryptionState EStoraged::findEncryptionStatus()
393 {
394     try
395     {
396         loadLuksHeader();
397         return Drive::DriveEncryptionState::Encrypted;
398     }
399     catch (...)
400     {
401         return Drive::DriveEncryptionState::Unencrypted;
402     }
403 }
404 
activateLuksDev(std::vector<uint8_t> password)405 void EStoraged::activateLuksDev(std::vector<uint8_t> password)
406 {
407     lg2::info("Activating LUKS dev {DEV}", "DEV", devPath, "REDFISH_MESSAGE_ID",
408               std::string("OpenBMC.0.1.ActivateLuksDev"));
409 
410     /* Create the handle. */
411     CryptHandle cryptHandle = loadLuksHeader();
412 
413     int retval = cryptIface->cryptActivateByPassphrase(
414         cryptHandle.get(), containerName.c_str(), CRYPT_ANY_SLOT,
415         reinterpret_cast<const char*>(password.data()), password.size(),
416         CRYPT_ACTIVATE_ALLOW_DISCARDS);
417 
418     if (retval < 0)
419     {
420         lg2::error("Failed to activate LUKS dev: {RETVAL}", "RETVAL", retval,
421                    "REDFISH_MESSAGE_ID",
422                    std::string("OpenBMC.0.1.ActivateLuksDevFail"));
423         throw InternalFailure();
424     }
425 
426     lg2::info("Successfully activated LUKS dev {DEV}", "DEV", devPath,
427               "REDFISH_MESSAGE_ID",
428               std::string("OpenBMC.0.1.ActivateLuksDevSuccess"));
429 }
430 
createFilesystem()431 void EStoraged::createFilesystem()
432 {
433     /* Run the command to create the filesystem. */
434     int retval = fsIface->runMkfs(cryptDevicePath, {"-E", "discard"});
435     if (retval != 0)
436     {
437         lg2::error("Failed to create filesystem: {RETVAL}", "RETVAL", retval,
438                    "REDFISH_MESSAGE_ID",
439                    std::string("OpenBMC.0.1.CreateFilesystemFail"));
440         throw InternalFailure();
441     }
442     lg2::info("Successfully created filesystem for {CONTAINER}", "CONTAINER",
443               cryptDevicePath, "REDFISH_MESSAGE_ID",
444               std::string("OpenBMC.0.1.CreateFilesystemSuccess"));
445 }
446 
mountFilesystem()447 void EStoraged::mountFilesystem()
448 {
449     /*
450      * Before mounting, run fsck to check for and resolve any filesystem errors.
451      */
452     int retval = fsIface->runFsck(cryptDevicePath, "-t ext4 -p");
453     if (retval != 0)
454     {
455         lg2::error("The fsck command failed: {RETVAL}", "RETVAL", retval,
456                    "REDFISH_MESSAGE_ID",
457                    std::string("OpenBMC.0.1.FixFilesystemFail"));
458         /* We'll still try to mount the filesystem, though. */
459     }
460 
461     /*
462      * Create directory for the filesystem, if it's not already present. It
463      * might already exist if, for example, the BMC reboots after creating the
464      * directory.
465      */
466     if (!fsIface->directoryExists(std::filesystem::path(mountPoint)))
467     {
468         bool success =
469             fsIface->createDirectory(std::filesystem::path(mountPoint));
470         if (!success)
471         {
472             lg2::error("Failed to create mount point: {DIR}", "DIR", mountPoint,
473                        "REDFISH_MESSAGE_ID",
474                        std::string("OpenBMC.0.1.MountFilesystemFail"));
475             throw InternalFailure();
476         }
477     }
478 
479     /* Run the command to mount the filesystem. */
480     retval = fsIface->doMount(cryptDevicePath.c_str(), mountPoint.c_str(),
481                               "ext4", 0, nullptr);
482     if (retval != 0)
483     {
484         lg2::error("Failed to mount filesystem: {RETVAL}", "RETVAL", retval,
485                    "REDFISH_MESSAGE_ID",
486                    std::string("OpenBMC.0.1.MountFilesystemFail"));
487         bool removeSuccess =
488             fsIface->removeDirectory(std::filesystem::path(mountPoint));
489         if (!removeSuccess)
490         {
491             lg2::error("Failed to remove mount point: {DIR}", "DIR", mountPoint,
492                        "REDFISH_MESSAGE_ID",
493                        std::string("OpenBMC.0.1.MountFilesystemFail"));
494         }
495         throw InternalFailure();
496     }
497 
498     lg2::info("Successfully mounted filesystem at {DIR}", "DIR", mountPoint,
499               "REDFISH_MESSAGE_ID",
500               std::string("OpenBMC.0.1.MountFilesystemSuccess"));
501 }
502 
unmountFilesystem()503 void EStoraged::unmountFilesystem()
504 {
505     int retval = fsIface->doUnmount(mountPoint.c_str());
506     if (retval != 0)
507     {
508         lg2::error("Failed to unmount filesystem: {RETVAL}", "RETVAL", retval,
509                    "REDFISH_MESSAGE_ID",
510                    std::string("OpenBMC.0.1.UnmountFilesystemFail"));
511         throw InternalFailure();
512     }
513 
514     /* Remove the mount point. */
515     bool success = fsIface->removeDirectory(std::filesystem::path(mountPoint));
516     if (!success)
517     {
518         lg2::error("Failed to remove mount point {DIR}", "DIR", mountPoint,
519                    "REDFISH_MESSAGE_ID",
520                    std::string("OpenBMC.0.1.UnmountFilesystemFail"));
521         throw InternalFailure();
522     }
523 
524     lg2::info("Successfully unmounted filesystem at {DIR}", "DIR", mountPoint,
525               "REDFISH_MESSAGE_ID",
526               std::string("OpenBMC.0.1.MountFilesystemSuccess"));
527 }
528 
deactivateLuksDev()529 void EStoraged::deactivateLuksDev()
530 {
531     lg2::info("Deactivating LUKS device {DEV}", "DEV", devPath,
532               "REDFISH_MESSAGE_ID",
533               std::string("OpenBMC.0.1.DeactivateLuksDev"));
534 
535     int retval = cryptIface->cryptDeactivate(nullptr, containerName.c_str());
536     if (retval < 0)
537     {
538         lg2::error("Failed to deactivate crypt device: {RETVAL}", "RETVAL",
539                    retval, "REDFISH_MESSAGE_ID",
540                    std::string("OpenBMC.0.1.DeactivateLuksDevFail"));
541         throw InternalFailure();
542     }
543 
544     lg2::info("Successfully deactivated LUKS device {DEV}", "DEV", devPath,
545               "REDFISH_MESSAGE_ID",
546               std::string("OpenBMC.0.1.DeactivateLuksDevSuccess"));
547 }
548 
getCryptDevicePath() const549 std::string_view EStoraged::getCryptDevicePath() const
550 {
551     return cryptDevicePath;
552 }
553 
enableBackgroundOperation(std::unique_ptr<stdplus::Fd> fd,std::string_view devPath)554 bool EStoraged::enableBackgroundOperation(std::unique_ptr<stdplus::Fd> fd,
555                                           std::string_view devPath)
556 {
557     struct mmc_ioc_cmd idata{};
558     memset(&idata, 0, sizeof(idata));
559     // Extended Device Specific Data. Contains information about the Device
560     // capabilities and selected modes.
561     std::array<uint8_t, /*EXT_CSD*/ 512> extCsd{};
562     idata.write_flag = 0;
563     idata.opcode = MMC_SEND_EXT_CSD;
564     idata.arg = 0;
565     idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
566     idata.blksz = extCsd.size();
567     idata.blocks = 1;
568     mmc_ioc_cmd_set_data(idata, extCsd.data());
569     if (fd->ioctl(MMC_IOC_CMD, &idata) != 0)
570     {
571         throw BkopsIoctlFailure(devPath,
572                                 "Failed to get Extended Device Specific Data");
573     }
574 
575     if ((extCsd[EXT_CSD_BKOPS_SUPPORT] & 0x1) == 0)
576     {
577         lg2::info("BKOPS is not supported for {DEV}", "DEV", devPath);
578         return false;
579     }
580     lg2::info("BKOPS is supported for {DEV}", "DEV", devPath);
581 
582     if ((extCsd[EXT_CSD_BKOPS_EN] &
583          (EXT_CSD_MANUAL_BKOPS_MASK | EXT_CSD_AUTO_BKOPS_MASK)) != 0)
584     {
585         lg2::info("BKOPS is already enabled for {DEV}: Mode: {MODE}", "DEV",
586                   devPath, "MODE", extCsd[EXT_CSD_BKOPS_EN]);
587         return false;
588     }
589 
590     // Clear the input data.
591     memset(&idata, 0, sizeof(idata));
592     idata.write_flag = 1;
593     idata.opcode = MMC_SWITCH;
594     idata.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | (EXT_CSD_BKOPS_EN << 16) |
595                 (EXT_CSD_MANUAL_BKOPS_MASK << 8) | EXT_CSD_CMD_SET_NORMAL;
596     idata.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
597     if (fd->ioctl(MMC_IOC_CMD, &idata) != 0)
598     {
599         throw BkopsEnableFailure(devPath);
600     }
601 
602     lg2::info("Successfully enable BKOPS for {DEV}", "DEV", devPath);
603     return true;
604 }
605 
606 } // namespace estoraged
607