1 /*
2 // Copyright (c) 2019 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include "utils.hpp"
18 
19 #include <boost/algorithm/string/replace.hpp>
20 #include <boost/asio/steady_timer.hpp>
21 #include <iostream>
22 #include <sdbusplus/asio/connection.hpp>
23 #include <sdbusplus/asio/object_server.hpp>
24 #include <sdbusplus/bus/match.hpp>
25 #include <string>
26 #include <utility>
27 
28 extern "C" {
29 #include <i2c/smbus.h>
30 #include <linux/i2c-dev.h>
31 }
32 
33 constexpr const char* configType =
34     "xyz.openbmc_project.Configuration.Intel_HSBP_CPLD";
35 
36 constexpr size_t scanRateSeconds = 5;
37 constexpr size_t maxDrives = 8; // only 1 byte alloted
38 
39 boost::asio::io_context io;
40 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
41 sdbusplus::asio::object_server objServer(conn);
42 
43 static std::string zeroPad(const uint8_t val)
44 {
45     std::ostringstream version;
46     version << std::setw(2) << std::setfill('0') << static_cast<size_t>(val);
47     return version.str();
48 }
49 
50 struct Drive
51 {
52     Drive(size_t driveIndex, bool isPresent, bool isOperational, bool nvme,
53           bool rebuilding) :
54         isNvme(nvme)
55     {
56         constexpr const char* basePath =
57             "/xyz/openbmc_project/inventory/item/drive/Drive_";
58         itemIface = objServer.add_interface(
59             basePath + std::to_string(driveIndex), inventory::interface);
60         itemIface->register_property("Present", isPresent);
61         itemIface->register_property("PrettyName",
62                                      "Drive " + std::to_string(driveIndex));
63         itemIface->initialize();
64         operationalIface = objServer.add_interface(
65             itemIface->get_object_path(),
66             "xyz.openbmc_project.State.Decorator.OperationalStatus");
67         operationalIface->register_property("Functional", isOperational);
68         operationalIface->initialize();
69         rebuildingIface = objServer.add_interface(
70             itemIface->get_object_path(), "xyz.openbmc_project.State.Drive");
71         rebuildingIface->register_property("Rebuilding", rebuilding);
72         rebuildingIface->initialize();
73     }
74     ~Drive()
75     {
76         objServer.remove_interface(itemIface);
77         objServer.remove_interface(operationalIface);
78         objServer.remove_interface(rebuildingIface);
79     }
80 
81     std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface;
82     std::shared_ptr<sdbusplus::asio::dbus_interface> operationalIface;
83     std::shared_ptr<sdbusplus::asio::dbus_interface> rebuildingIface;
84     bool isNvme;
85 };
86 
87 struct Backplane
88 {
89 
90     Backplane(size_t busIn, size_t addressIn, size_t backplaneIndexIn,
91               const std::string& nameIn) :
92         bus(busIn),
93         address(addressIn), backplaneIndex(backplaneIndexIn - 1), name(nameIn),
94         timer(std::make_shared<boost::asio::steady_timer>(io))
95     {
96     }
97     void run()
98     {
99         file = open(("/dev/i2c-" + std::to_string(bus)).c_str(), O_RDWR);
100         if (file < 0)
101         {
102             std::cerr << "unable to open bus " << bus << "\n";
103             return;
104         }
105 
106         if (ioctl(file, I2C_SLAVE_FORCE, address) < 0)
107         {
108             std::cerr << "unable to set address to " << address << "\n";
109             return;
110         }
111 
112         if (!getPresent())
113         {
114             std::cerr << "Cannot detect CPLD\n";
115             return;
116         }
117 
118         getBootVer(bootVer);
119         getFPGAVer(fpgaVer);
120         getSecurityRev(securityRev);
121         std::string dbusName = boost::replace_all_copy(name, " ", "_");
122         hsbpItemIface = objServer.add_interface(
123             "/xyz/openbmc_project/inventory/item/hsbp/" + dbusName,
124             inventory::interface);
125         hsbpItemIface->register_property("Present", true);
126         hsbpItemIface->register_property("PrettyName", name);
127         hsbpItemIface->initialize();
128 
129         versionIface =
130             objServer.add_interface(hsbpItemIface->get_object_path(),
131                                     "xyz.openbmc_project.Software.Version");
132         versionIface->register_property("Version", zeroPad(bootVer) + "." +
133                                                        zeroPad(fpgaVer) + "." +
134                                                        zeroPad(securityRev));
135         versionIface->register_property(
136             "Purpose",
137             std::string(
138                 "xyz.openbmc_project.Software.Version.VersionPurpose.HSBP"));
139         versionIface->initialize();
140         getPresence(presence);
141         getIFDET(ifdet);
142 
143         createDrives();
144 
145         runTimer();
146     }
147 
148     void runTimer()
149     {
150         timer->expires_after(std::chrono::seconds(scanRateSeconds));
151         timer->async_wait([this](boost::system::error_code ec) {
152             if (ec == boost::asio::error::operation_aborted)
153             {
154                 // we're being destroyed
155                 return;
156             }
157             else if (ec)
158             {
159                 std::cerr << "timer error " << ec.message() << "\n";
160                 return;
161             }
162             uint8_t curPresence = 0;
163             uint8_t curIFDET = 0;
164             uint8_t curFailed = 0;
165             uint8_t curRebuild = 0;
166 
167             getPresence(curPresence);
168             getIFDET(curIFDET);
169             getFailed(curFailed);
170             getRebuild(curRebuild);
171 
172             if (curPresence != presence || curIFDET != ifdet ||
173                 curFailed != failed || curRebuild != rebuilding)
174             {
175                 presence = curPresence;
176                 ifdet = curIFDET;
177                 failed = curFailed;
178                 rebuilding = curRebuild;
179                 updateDrives();
180             }
181             runTimer();
182         });
183     }
184 
185     void createDrives()
186     {
187         uint8_t nvme = ifdet ^ presence;
188         for (size_t ii = 0; ii < maxDrives; ii++)
189         {
190             bool isNvme = nvme & (1 << ii);
191             bool isPresent = isNvme || (presence & (1 << ii));
192             bool isFailed = !isPresent || failed & (1 << ii);
193             bool isRebuilding = !isPresent && (rebuilding & (1 << ii));
194 
195             // +1 to convert from 0 based to 1 based
196             size_t driveIndex = (backplaneIndex * maxDrives) + ii + 1;
197             drives.emplace_back(driveIndex, isPresent, !isFailed, isNvme,
198                                 isRebuilding);
199         }
200     }
201 
202     void updateDrives()
203     {
204 
205         uint8_t nvme = ifdet ^ presence;
206         for (size_t ii = 0; ii < maxDrives; ii++)
207         {
208             bool isNvme = nvme & (1 << ii);
209             bool isPresent = isNvme || (presence & (1 << ii));
210             bool isFailed = !isPresent || (failed & (1 << ii));
211             bool isRebuilding = isPresent && (rebuilding & (1 << ii));
212 
213             Drive& drive = drives[ii];
214             drive.isNvme = isNvme;
215             drive.itemIface->set_property("Present", isPresent);
216             drive.operationalIface->set_property("Functional", !isFailed);
217             drive.rebuildingIface->set_property("Rebuilding", isRebuilding);
218         }
219     }
220 
221     bool getPresent()
222     {
223         present = i2c_smbus_read_byte(file) >= 0;
224         return present;
225     }
226 
227     bool getTypeID(uint8_t& val)
228     {
229         constexpr uint8_t addr = 2;
230         int ret = i2c_smbus_read_byte_data(file, addr);
231         if (ret < 0)
232         {
233             std::cerr << "Error " << __FUNCTION__ << "\n";
234             return false;
235         }
236         val = static_cast<uint8_t>(ret);
237         return true;
238     }
239 
240     bool getBootVer(uint8_t& val)
241     {
242         constexpr uint8_t addr = 3;
243         int ret = i2c_smbus_read_byte_data(file, addr);
244         if (ret < 0)
245         {
246             std::cerr << "Error " << __FUNCTION__ << "\n";
247             return false;
248         }
249         val = static_cast<uint8_t>(ret);
250         return true;
251     }
252 
253     bool getFPGAVer(uint8_t& val)
254     {
255         constexpr uint8_t addr = 4;
256         int ret = i2c_smbus_read_byte_data(file, addr);
257         if (ret < 0)
258         {
259             std::cerr << "Error " << __FUNCTION__ << "\n";
260             return false;
261         }
262         val = static_cast<uint8_t>(ret);
263         return true;
264     }
265 
266     bool getSecurityRev(uint8_t& val)
267     {
268         constexpr uint8_t addr = 5;
269         int ret = i2c_smbus_read_byte_data(file, addr);
270         if (ret < 0)
271         {
272             std::cerr << "Error " << __FUNCTION__ << "\n";
273             return false;
274         }
275         val = static_cast<uint8_t>(ret);
276         return true;
277     }
278 
279     bool getPresence(uint8_t& val)
280     {
281         // NVMe drives do not assert PRSNTn, and as such do not get reported as
282         // PRESENT in this register
283 
284         constexpr uint8_t addr = 8;
285 
286         int ret = i2c_smbus_read_byte_data(file, addr);
287         if (ret < 0)
288         {
289             std::cerr << "Error " << __FUNCTION__ << "\n";
290             return false;
291         }
292         // presence is inverted
293         val = static_cast<uint8_t>(~ret);
294         return true;
295     }
296 
297     bool getIFDET(uint8_t& val)
298     {
299         // This register is a bitmap of parallel GPIO pins connected to the
300         // IFDETn pin of a drive slot. SATA, SAS, and NVMe drives all assert
301         // IFDETn low when they are inserted into the HSBP.This register, in
302         // combination with the PRESENCE register, are used by the BMC to detect
303         // the presence of NVMe drives.
304 
305         constexpr uint8_t addr = 9;
306 
307         int ret = i2c_smbus_read_byte_data(file, addr);
308         if (ret < 0)
309         {
310             std::cerr << "Error " << __FUNCTION__ << "\n";
311             return false;
312         }
313         // ifdet is inverted
314         val = static_cast<uint8_t>(~ret);
315         return true;
316     }
317 
318     bool getFailed(uint8_t& val)
319     {
320         constexpr uint8_t addr = 0xC;
321         int ret = i2c_smbus_read_byte_data(file, addr);
322         if (ret < 0)
323         {
324             std::cerr << "Error " << __FUNCTION__ << "\n";
325             return false;
326         }
327         val = static_cast<uint8_t>(ret);
328         return true;
329     }
330 
331     bool getRebuild(uint8_t& val)
332     {
333         constexpr uint8_t addr = 0xD;
334         int ret = i2c_smbus_read_byte_data(file, addr);
335         if (ret < 0)
336         {
337             std::cerr << "Error " << __FUNCTION__ << "\n";
338             return false;
339         }
340         val = static_cast<uint8_t>(ret);
341         return true;
342     }
343 
344     ~Backplane()
345     {
346         objServer.remove_interface(hsbpItemIface);
347         objServer.remove_interface(versionIface);
348         if (file >= 0)
349         {
350             close(file);
351         }
352     }
353 
354     size_t bus;
355     size_t address;
356     size_t backplaneIndex;
357     std::string name;
358     std::shared_ptr<boost::asio::steady_timer> timer;
359     bool present = false;
360     uint8_t typeId = 0;
361     uint8_t bootVer = 0;
362     uint8_t fpgaVer = 0;
363     uint8_t securityRev = 0;
364     uint8_t funSupported = 0;
365     uint8_t presence = 0;
366     uint8_t ifdet = 0;
367     uint8_t failed = 0;
368     uint8_t rebuilding = 0;
369 
370     int file = -1;
371 
372     std::string type;
373 
374     std::shared_ptr<sdbusplus::asio::dbus_interface> hsbpItemIface;
375     std::shared_ptr<sdbusplus::asio::dbus_interface> versionIface;
376 
377     std::vector<Drive> drives;
378 };
379 
380 std::unordered_map<std::string, Backplane> backplanes;
381 
382 void populate()
383 {
384     conn->async_method_call(
385         [](const boost::system::error_code ec, const GetSubTreeType& subtree) {
386             if (ec)
387             {
388                 std::cerr << "Error contacting mapper " << ec.message() << "\n";
389                 return;
390             }
391             for (const auto& [path, objDict] : subtree)
392             {
393                 if (objDict.empty())
394                 {
395                     continue;
396                 }
397 
398                 const std::string& owner = objDict.begin()->first;
399                 conn->async_method_call(
400                     [path](const boost::system::error_code ec2,
401                            const boost::container::flat_map<
402                                std::string, BasicVariantType>& resp) {
403                         if (ec2)
404                         {
405                             std::cerr << "Error Getting Config "
406                                       << ec2.message() << "\n";
407                             return;
408                         }
409                         backplanes.clear();
410                         std::optional<size_t> bus;
411                         std::optional<size_t> address;
412                         std::optional<size_t> backplaneIndex;
413                         std::optional<std::string> name;
414                         for (const auto& [key, value] : resp)
415                         {
416                             if (key == "Bus")
417                             {
418                                 bus = std::get<uint64_t>(value);
419                             }
420                             else if (key == "Address")
421                             {
422                                 address = std::get<uint64_t>(value);
423                             }
424                             else if (key == "Index")
425                             {
426                                 backplaneIndex = std::get<uint64_t>(value);
427                             }
428                             else if (key == "Name")
429                             {
430                                 name = std::get<std::string>(value);
431                             }
432                         }
433                         if (!bus || !address || !name || !backplaneIndex)
434                         {
435                             std::cerr << "Illegal configuration at " << path
436                                       << "\n";
437                             return;
438                         }
439                         const auto& [backplane, status] = backplanes.emplace(
440                             *name,
441                             Backplane(*bus, *address, *backplaneIndex, *name));
442                         backplane->second.run();
443                     },
444                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",
445                     configType);
446             }
447         },
448         mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
449         0, std::array<const char*, 1>{configType});
450 }
451 
452 int main()
453 {
454     boost::asio::steady_timer callbackTimer(io);
455 
456     conn->request_name("xyz.openbmc_project.HsbpManager");
457 
458     sdbusplus::bus::match::match match(
459         *conn,
460         "type='signal',member='PropertiesChanged',arg0='" +
461             std::string(configType) + "'",
462         [&callbackTimer](sdbusplus::message::message&) {
463             callbackTimer.expires_after(std::chrono::seconds(2));
464             callbackTimer.async_wait([](const boost::system::error_code ec) {
465                 if (ec == boost::asio::error::operation_aborted)
466                 {
467                     // timer was restarted
468                     return;
469                 }
470                 else if (ec)
471                 {
472                     std::cerr << "Timer error" << ec.message() << "\n";
473                     return;
474                 }
475                 populate();
476             });
477         });
478 
479     io.post([]() { populate(); });
480     io.run();
481 }
482