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