xref: /openbmc/openpower-proc-control/procedures/phal/reinit_devtree.cpp (revision 90166c15c815b667a73026be0d2553f2256f4dce)
1 #include "config.h"
2 
3 #include "extensions/phal/create_pel.hpp"
4 #include "registration.hpp"
5 #include "temporary_file.hpp"
6 
7 #include <fcntl.h>
8 #include <fmt/format.h>
9 
10 #include <nlohmann/json.hpp>
11 #include <phosphor-logging/elog-errors.hpp>
12 
13 #include <cstdio>
14 #include <filesystem>
15 
16 extern "C"
17 {
18 #include <dtree.h>
19 }
20 
21 namespace openpower
22 {
23 namespace phal
24 {
25 using namespace phosphor::logging;
26 using FILE_Ptr = std::unique_ptr<FILE, decltype(&::fclose)>;
27 namespace fs = std::filesystem;
28 
29 void applyAttrOverride(fs::path& devtreeFile)
30 {
31     constexpr auto DEVTREE_ATTR_OVERRIDE_PATH = "/tmp/devtree_attr_override";
32     auto overrideFile = fs::path(DEVTREE_ATTR_OVERRIDE_PATH);
33     if (!fs::exists(overrideFile))
34     {
35         return;
36     }
37 
38     // Open attribute override file in r/o mode
39     FILE_Ptr fpOverride(fopen(DEVTREE_ATTR_OVERRIDE_PATH, "r"), fclose);
40 
41     // Update Devtree with attribute override data.
42     auto ret = dtree_cronus_import(devtreeFile.c_str(), CEC_INFODB_PATH,
43                                    fpOverride.get());
44     if (ret)
45     {
46         log<level::ERR>(
47             fmt::format("Failed({}) to update attribute override data", ret)
48                 .c_str());
49         throw std::runtime_error(
50             "applyAttrOverride: dtree_cronus_import failed");
51     }
52     log<level::INFO>("DEVTREE: Applied attribute override data");
53 }
54 
55 /**
56  * @brief Compute RO device tree file path from RW symbolic link
57  * @return RO file path one failure exception will be thrown
58  */
59 fs::path computeRODeviceTreePath()
60 {
61     // Symbolic links are not created for RO files, compute the lid name
62     // for the RW symbolic link and use it to compute RO file.
63     // Example:
64     // RW file = /media/hostfw/running/DEVTREE -> 81e00672.lid
65     // RO file = /media/hostfw/running-ro/ + 81e00672.lid
66     fs::path rwFileName = fs::read_symlink(CEC_DEVTREE_RW_PATH);
67     if (rwFileName.empty())
68     {
69         std::string err =
70             fmt::format("Failed to read the target file "
71                         "for the RW device tree symbolic link ({})",
72                         CEC_DEVTREE_RW_PATH);
73         log<level::ERR>(err.c_str());
74         throw std::runtime_error(err);
75     }
76     fs::path roFilePath = CEC_DEVTREE_RO_BASE_PATH / rwFileName;
77     if (!fs::exists(roFilePath))
78     {
79         auto err = fmt::format("RO device tree file ({}) does not "
80                                "exit ",
81                                roFilePath.string());
82         log<level::ERR>(err.c_str());
83         throw std::runtime_error(err);
84     }
85     return roFilePath;
86 }
87 
88 /**
89  * @brief reinitialize the devtree attributes.
90  * In the regular host boot path devtree attribute need to
91  * initialize the default data and also some of the selected
92  * attributes need to preserve with previous boot value.
93  * Preserve attribute list is available BMC pre-defined location.
94  * This function helps to meet the host ipl requirement
95  * related to attribute persistency management for host ipl.
96  * Steps involved
97  * 1. Create attribute data file from devtree r/w version based on
98  *    the reinit attribute list file bmc /usr/share/pdata path.
99  * 2. Create temporary devtree file by copying devtree r/o file
100  * 3. Override temporary copy of devtree with attribute data file
101  *    from step 1.
102  * 3a. Apply user provided attribute override if present in the
103  *     predefined location.
104  * 4. Copy  temporary copy devtree to r/w devtree version file.
105  */
106 
107 void reinitDevtree()
108 {
109     using json = nlohmann::json;
110 
111     log<level::INFO>("reinitDevtree: started");
112 
113     // All the file operations is done on temporary copy
114     // This is to avoid any file corruption issue during
115     // copy or attribute import path.
116     openpower::util::TemporaryFile tmpDevtreeFile{};
117     const auto copyOptions = std::filesystem::copy_options::overwrite_existing;
118     auto tmpDevtreePath = tmpDevtreeFile.getPath();
119     bool tmpReinitDone = false;
120     // To store callouts details in json format as per pel expectation.
121     json jsonCalloutDataList;
122     jsonCalloutDataList = json::array();
123 
124     try
125     {
126         // Check devtree reinit attributes list file is present
127         auto attrFile = fs::path(DEVTREE_REINIT_ATTRS_LIST);
128         if (!fs::exists(attrFile))
129         {
130             log<level::ERR>(
131                 fmt::format(
132                     "devtree attribute export list file is not available: ({})",
133                     DEVTREE_REINIT_ATTRS_LIST)
134                     .c_str());
135             throw std::runtime_error("reinitDevtree: missing export list file");
136         }
137 
138         // create temporary data file to store the devtree export data
139         openpower::util::TemporaryFile tmpFile{};
140 
141         {
142             // get temporary datafile pointer.
143             FILE_Ptr fpExport(fopen(tmpFile.getPath().c_str(), "w+"), fclose);
144 
145             if (fpExport.get() == nullptr)
146             {
147                 log<level::ERR>(
148                     fmt::format("Temporary data file failed to open: ({})",
149                                 tmpFile.getPath().c_str())
150                         .c_str());
151                 throw std::runtime_error(
152                     "reinitDevtree: failed to open temporaray data file");
153             }
154 
155             // Step 1: export devtree data based on the reinit attribute list.
156             auto ret =
157                 dtree_cronus_export(CEC_DEVTREE_RW_PATH, CEC_INFODB_PATH,
158                                     DEVTREE_REINIT_ATTRS_LIST, fpExport.get());
159             if (ret)
160             {
161                 log<level::ERR>(
162                     fmt::format("Failed({}) to collect attribute export data",
163                                 ret)
164                         .c_str());
165                 throw std::runtime_error(
166                     "reinitDevtree: dtree_cronus_export function failed");
167             }
168         }
169 
170         // Step 2: Create temporary devtree file by copying devtree r/o version
171         fs::path roFilePath = computeRODeviceTreePath();
172         std::filesystem::copy(roFilePath, tmpDevtreePath, copyOptions);
173 
174         // get r/o version data file pointer
175         FILE_Ptr fpImport(fopen(tmpFile.getPath().c_str(), "r"), fclose);
176         if (fpImport.get() == nullptr)
177         {
178             log<level::ERR>(
179                 fmt::format("import, temporary data file failed to open: ({})",
180                             tmpFile.getPath().c_str())
181                     .c_str());
182             throw std::runtime_error(
183                 "reinitDevtree: import, failed to open temporaray data file");
184         }
185 
186         // Step 3: Update Devtree r/w version with data file attribute data.
187         auto ret = dtree_cronus_import(tmpDevtreePath.c_str(), CEC_INFODB_PATH,
188                                        fpImport.get());
189         if (ret)
190         {
191             log<level::ERR>(
192                 fmt::format("Failed({}) to update attribute data", ret)
193                     .c_str());
194             throw std::runtime_error(
195                 "reinitDevtree: dtree_cronus_import function failed");
196         }
197         // Step 3.a: Apply user provided attribute override data if present.
198         applyAttrOverride(tmpDevtreePath);
199 
200         // Temporary file reinit is success.
201         tmpReinitDone = true;
202     }
203     catch (const std::exception& e)
204     {
205         // Any failures during temporary file re-init should create PEL
206         // and continue with current version of devtree file to allow boot.
207         log<level::ERR>(
208             fmt::format("reinitDevtree failed ({})", e.what()).c_str());
209         json jsonCalloutDataList;
210         jsonCalloutDataList = json::array();
211         json jsonCalloutData;
212         jsonCalloutData["Procedure"] = "BMC0001";
213         jsonCalloutData["Priority"] = "M";
214         jsonCalloutDataList.emplace_back(jsonCalloutData);
215         openpower::pel::createErrorPEL(
216             "org.open_power.PHAL.Error.devtreeReinit", jsonCalloutDataList);
217     }
218 
219     // Step 4: Update devtree r/w file
220     try
221     {
222         if (tmpReinitDone)
223         {
224             // Step 4: Copy temporary version devtree file r/w version file.
225             // Any copy failures should results service failure.
226             std::filesystem::copy(tmpDevtreePath, CEC_DEVTREE_RW_PATH,
227                                   copyOptions);
228             log<level::INFO>("reinitDevtree: completed successfully");
229         }
230         else
231         {
232             // Attempt boot with genesis mode attribute data.
233             fs::path roFilePath = computeRODeviceTreePath();
234             log<level::WARNING>("reinitDevtree: DEVTREE(r/w) initilizing with "
235                                 "genesis mode attribute data");
236             std::filesystem::copy(roFilePath, CEC_DEVTREE_RW_PATH, copyOptions);
237         }
238     }
239     catch (const std::exception& e)
240     {
241         // Any failures during update on r/w file is serious error should create
242         // PEL, with code callout. Also failed the service.
243         // and continue with current version of devtree file to allow boot.
244         log<level::ERR>(
245             fmt::format("reinitDevtree r/w version update failed ({})",
246                         e.what())
247                 .c_str());
248         json jsonCalloutDataList;
249         jsonCalloutDataList = json::array();
250         json jsonCalloutData;
251         jsonCalloutData["Procedure"] = "BMC0001";
252         jsonCalloutData["Priority"] = "H";
253         jsonCalloutDataList.emplace_back(jsonCalloutData);
254         openpower::pel::createErrorPEL(
255             "org.open_power.PHAL.Error.devtreeReinit", jsonCalloutDataList);
256         throw;
257     }
258 }
259 
260 REGISTER_PROCEDURE("reinitDevtree", reinitDevtree)
261 
262 } // namespace phal
263 } // namespace openpower
264