xref: /openbmc/openpower-proc-control/procedures/phal/reinit_devtree.cpp (revision e0dd7af42a30b89e9f820c999bfe4a9bacc2a01d)
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 
9 #include <nlohmann/json.hpp>
10 #include <phosphor-logging/elog-errors.hpp>
11 
12 #include <cstdio>
13 #include <filesystem>
14 #include <format>
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             std::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             std::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 = std::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     using Severity =
111         sdbusplus::xyz::openbmc_project::Logging::server::Entry::Level;
112 
113     log<level::INFO>("reinitDevtree: started");
114 
115     // All the file operations is done on temporary copy
116     // This is to avoid any file corruption issue during
117     // copy or attribute import path.
118     openpower::util::TemporaryFile tmpDevtreeFile{};
119     const auto copyOptions = std::filesystem::copy_options::overwrite_existing;
120     auto tmpDevtreePath = tmpDevtreeFile.getPath();
121     bool tmpReinitDone = false;
122     // To store callouts details in json format as per pel expectation.
123     json jsonCalloutDataList;
124     jsonCalloutDataList = json::array();
125 
126     try
127     {
128         // Check devtree reinit attributes list file is present
129         auto attrFile = fs::path(DEVTREE_REINIT_ATTRS_LIST);
130         if (!fs::exists(attrFile))
131         {
132             log<level::ERR>(
133                 std::format(
134                     "devtree attribute export list file is not available: ({})",
135                     DEVTREE_REINIT_ATTRS_LIST)
136                     .c_str());
137             throw std::runtime_error("reinitDevtree: missing export list file");
138         }
139 
140         // create temporary data file to store the devtree export data
141         openpower::util::TemporaryFile tmpFile{};
142 
143         {
144             // get temporary datafile pointer.
145             FILE_Ptr fpExport(fopen(tmpFile.getPath().c_str(), "w+"), fclose);
146 
147             if (fpExport.get() == nullptr)
148             {
149                 log<level::ERR>(
150                     std::format("Temporary data file failed to open: ({})",
151                                 tmpFile.getPath().c_str())
152                         .c_str());
153                 throw std::runtime_error(
154                     "reinitDevtree: failed to open temporaray data file");
155             }
156 
157             // Step 1: export devtree data based on the reinit attribute list.
158             auto ret = dtree_cronus_export(CEC_DEVTREE_RW_PATH, CEC_INFODB_PATH,
159                                            DEVTREE_REINIT_ATTRS_LIST,
160                                            fpExport.get());
161             if (ret)
162             {
163                 log<level::ERR>(
164                     std::format("Failed({}) to collect attribute export data",
165                                 ret)
166                         .c_str());
167                 throw std::runtime_error(
168                     "reinitDevtree: dtree_cronus_export function failed");
169             }
170         }
171 
172         // Step 2: Create temporary devtree file by copying devtree r/o version
173         fs::path roFilePath = computeRODeviceTreePath();
174         std::filesystem::copy(roFilePath, tmpDevtreePath, copyOptions);
175 
176         // get r/o version data file pointer
177         FILE_Ptr fpImport(fopen(tmpFile.getPath().c_str(), "r"), fclose);
178         if (fpImport.get() == nullptr)
179         {
180             log<level::ERR>(
181                 std::format("import, temporary data file failed to open: ({})",
182                             tmpFile.getPath().c_str())
183                     .c_str());
184             throw std::runtime_error(
185                 "reinitDevtree: import, failed to open temporaray data file");
186         }
187 
188         // Step 3: Update Devtree r/w version with data file attribute data.
189         auto ret = dtree_cronus_import(tmpDevtreePath.c_str(), CEC_INFODB_PATH,
190                                        fpImport.get());
191         if (ret)
192         {
193             log<level::ERR>(
194                 std::format("Failed({}) to update attribute data", ret)
195                     .c_str());
196             throw std::runtime_error(
197                 "reinitDevtree: dtree_cronus_import function failed");
198         }
199         // Step 3.a: Apply user provided attribute override data if present.
200         applyAttrOverride(tmpDevtreePath);
201 
202         // Temporary file reinit is success.
203         tmpReinitDone = true;
204     }
205     catch (const std::exception& e)
206     {
207         // Any failures during temporary file re-init should create PEL
208         // and continue with current version of devtree file to allow boot.
209         log<level::ERR>(
210             std::format("reinitDevtree failed ({})", e.what()).c_str());
211         json jsonCalloutDataList;
212         jsonCalloutDataList = json::array();
213         json jsonCalloutData;
214         jsonCalloutData["Procedure"] = "BMC0001";
215         jsonCalloutData["Priority"] = "M";
216         jsonCalloutDataList.emplace_back(jsonCalloutData);
217         openpower::pel::createErrorPEL(
218             "org.open_power.PHAL.Error.devtreeReinit", jsonCalloutDataList, {},
219             Severity::Error);
220     }
221 
222     // Step 4: Update devtree r/w file
223     try
224     {
225         if (tmpReinitDone)
226         {
227             // Step 4: Copy temporary version devtree file r/w version file.
228             // Any copy failures should results service failure.
229             std::filesystem::copy(tmpDevtreePath, CEC_DEVTREE_RW_PATH,
230                                   copyOptions);
231             log<level::INFO>("reinitDevtree: completed successfully");
232         }
233         else
234         {
235             // Attempt boot with genesis mode attribute data.
236             fs::path roFilePath = computeRODeviceTreePath();
237             log<level::WARNING>("reinitDevtree: DEVTREE(r/w) initilizing with "
238                                 "genesis mode attribute data");
239             std::filesystem::copy(roFilePath, CEC_DEVTREE_RW_PATH, copyOptions);
240         }
241     }
242     catch (const std::exception& e)
243     {
244         // Any failures during update on r/w file is serious error should create
245         // PEL, with code callout. Also failed the service.
246         // and continue with current version of devtree file to allow boot.
247         log<level::ERR>(
248             std::format("reinitDevtree r/w version update failed ({})",
249                         e.what())
250                 .c_str());
251         json jsonCalloutDataList;
252         jsonCalloutDataList = json::array();
253         json jsonCalloutData;
254         jsonCalloutData["Procedure"] = "BMC0001";
255         jsonCalloutData["Priority"] = "H";
256         jsonCalloutDataList.emplace_back(jsonCalloutData);
257         openpower::pel::createErrorPEL(
258             "org.open_power.PHAL.Error.devtreeReinit", jsonCalloutDataList, {},
259             Severity::Error);
260         throw;
261     }
262 }
263 
264 REGISTER_PROCEDURE("reinitDevtree", reinitDevtree)
265 
266 } // namespace phal
267 } // namespace openpower
268