1 /**
2  * Copyright © 2020 IBM 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 "extensions/openpower-pels/device_callouts.hpp"
18 #include "extensions/openpower-pels/paths.hpp"
19 
20 #include <fstream>
21 
22 #include <gtest/gtest.h>
23 
24 using namespace openpower::pels;
25 using namespace openpower::pels::device_callouts;
26 namespace fs = std::filesystem;
27 
28 // The callout JSON looks like:
29 // "I2C":
30 //   "<bus>":
31 //     "<address>":
32 //       "Callouts": ...
33 //
34 // "FSI":
35 //   "<fsi link>":
36 //     "Callouts": ...
37 //
38 // "FSI-I2C":
39 //    "<fsi link>":
40 //      "<bus>":
41 //        "<address>":
42 //          "Callouts": ...
43 //
44 // "FSI-SPI":
45 //    "<fsi link>":
46 //      "<bus>":
47 //        "Callouts": ...
48 
49 const auto calloutJSON = R"(
50 {
51     "I2C":
52     {
53         "0":
54         {
55             "32":
56             {
57                 "Callouts":[
58                     {
59                        "Name": "/chassis/motherboard/cpu0",
60                        "LocationCode": "P1-C19",
61                        "Priority": "H"
62                     }
63                 ],
64                 "Dest": "proc-0 target"
65             },
66             "81":
67             {
68                 "Callouts":[
69                     {
70                        "Name": "/chassis/motherboard/cpu0",
71                        "LocationCode": "P1-C19",
72                        "Priority": "H"
73                     }
74                 ],
75                 "Dest": "proc-0 target"
76             },
77             "90":
78             {
79                 "Callouts":[
80                     {
81                        "Name": "This is missing the location code",
82                        "Priority": "H"
83                     }
84                 ],
85                 "Dest": "proc-0 target"
86             }
87         },
88         "14":
89         {
90             "112":
91             {
92                 "Callouts":[
93                     {
94                        "Name": "/chassis/motherboard/cpu0",
95                        "LocationCode": "P1-C19",
96                        "Priority": "H"
97                     }
98                 ],
99                 "Dest": "proc-0 target"
100             },
101             "114":
102             {
103                 "Callouts":[
104                     {
105                        "Name": "/chassis/motherboard/cpu0",
106                        "LocationCode": "P1-C19",
107                        "Priority": "H",
108                        "MRU": "core0"
109                     },
110                     {
111                        "Name": "/chassis/motherboard",
112                        "LocationCode": "P1",
113                        "Priority": "M"
114                     }
115                 ],
116                 "Dest": "proc-0 target"
117             }
118         }
119     },
120     "FSI":
121     {
122         "0":
123         {
124            "Callouts":[
125                 {
126                     "Name": "/chassis/motherboard/cpu0",
127                     "LocationCode": "P1-C19",
128                     "Priority": "H"
129                 }
130            ],
131            "Dest": "proc-0 target"
132         },
133         "0-1":
134         {
135            "Callouts":[
136                 {
137                     "Name": "/chassis/motherboard/cpu0",
138                     "LocationCode": "P1-C19",
139                     "Priority": "H",
140                     "MRU": "core"
141                 }
142            ],
143            "Dest": "proc-0 target"
144         }
145     },
146     "FSI-I2C":
147     {
148         "0-3":
149         {
150            "7":
151            {
152               "24":
153               {
154                  "Callouts":[
155                     {
156                        "Name": "/chassis/motherboard/cpu0",
157                        "LocationCode": "P1-C19",
158                        "Priority": "H"
159                     }
160                  ],
161                  "Dest": "proc-0 target"
162               },
163               "25":
164               {
165                  "Callouts":[
166                     {
167                        "Name": "/chassis/motherboard/cpu5",
168                        "LocationCode": "P1-C25",
169                        "Priority": "H"
170                     },
171                     {
172                        "Name": "/chassis/motherboard",
173                        "LocationCode": "P1",
174                        "Priority": "M"
175                     },
176                     {
177                         "Name": "/chassis/motherboard/bmc",
178                         "LocationCode": "P2",
179                         "Priority": "L"
180                     }
181                  ],
182                  "Dest": "proc-5 target"
183               }
184            }
185         }
186     },
187     "FSI-SPI":
188     {
189         "8":
190         {
191             "3":
192             {
193                 "Callouts":[
194                     {
195                        "Name": "/chassis/motherboard/cpu0",
196                        "LocationCode": "P1-C19",
197                        "Priority": "H"
198                     }
199                 ],
200                 "Dest": "proc-0 target"
201             },
202             "4":
203             {
204                 "Callouts":[
205                     {
206                        "Name": "/chassis/motherboard/cpu2",
207                        "LocationCode": "P1-C12",
208                        "Priority": "M"
209                     }
210                 ],
211                 "Dest": "proc-0 target"
212             }
213         }
214     }
215 })"_json;
216 
217 class DeviceCalloutsTest : public ::testing::Test
218 {
219   public:
220     static void SetUpTestCase()
221     {
222         dataPath = getPELReadOnlyDataPath();
223         std::ofstream file{dataPath / filename};
224         file << calloutJSON.dump();
225     }
226 
227     static void TearDownTestCase()
228     {
229         fs::remove_all(dataPath);
230     }
231 
232     static std::string filename;
233     static fs::path dataPath;
234 };
235 
236 std::string DeviceCalloutsTest::filename = "systemA_dev_callouts.json";
237 fs::path DeviceCalloutsTest::dataPath;
238 
239 namespace openpower::pels::device_callouts
240 {
241 
242 // Helpers to compair vectors of Callout objects
243 bool operator!=(const Callout& left, const Callout& right)
244 {
245     return (left.priority != right.priority) ||
246            (left.locationCode != right.locationCode) ||
247            (left.name != right.name) || (left.mru != right.mru) ||
248            (left.debug != right.debug);
249 }
250 
251 bool operator==(const std::vector<Callout>& left,
252                 const std::vector<Callout>& right)
253 {
254     if (left.size() != right.size())
255     {
256         return false;
257     }
258 
259     for (size_t i = 0; i < left.size(); i++)
260     {
261         if (left[i] != right[i])
262         {
263             return false;
264         }
265     }
266 
267     return true;
268 }
269 
270 } // namespace openpower::pels::device_callouts
271 
272 // Test looking up the JSON file based on the system compatible names
273 TEST_F(DeviceCalloutsTest, getJSONFilenameTest)
274 {
275     {
276         std::vector<std::string> compatibles{"system1", "systemA", "system3"};
277         EXPECT_EQ(util::getJSONFilename(compatibles),
278                   fs::path{dataPath / filename});
279     }
280 
281     // Actual filename not in compatibles
282     {
283         std::vector<std::string> compatibles{"system5", "system6"};
284         EXPECT_THROW(util::getJSONFilename(compatibles), std::invalid_argument);
285     }
286 
287     // Test using the fallback name
288     {
289         // If _dev_callouts.json is there, it will be used if no other
290         // match is found.
291         fs::path fallbackFile{dataPath / "_dev_callouts.json"};
292         std::ofstream file{fallbackFile};
293         file << calloutJSON.dump();
294         file.close();
295 
296         // Fallback shouldn't be used because the actual systemA file is there
297         {
298             std::vector<std::string> compatibles{"system1", "systemA",
299                                                  "system3"};
300             EXPECT_EQ(util::getJSONFilename(compatibles),
301                       fs::path{dataPath / filename});
302         }
303 
304         // Fallback should be used because no other match
305         {
306             std::vector<std::string> compatibles{"systemX", "systemY"};
307             EXPECT_EQ(util::getJSONFilename(compatibles), fallbackFile);
308         }
309     }
310 }
311 
312 // Test determining the callout type from the device path
313 TEST_F(DeviceCalloutsTest, getCalloutTypeTest)
314 {
315     // Invalid
316     {
317         EXPECT_EQ(util::getCalloutType("/some/bad/device/path"),
318                   util::CalloutType::unknown);
319     }
320 
321     // I2C
322     {
323         EXPECT_EQ(util::getCalloutType(
324                       "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/"
325                       "1e78a340.i2c-bus/i2c-14/14-0072"),
326                   util::CalloutType::i2c);
327     }
328 
329     // FSI
330     {
331         EXPECT_EQ(util::getCalloutType(
332                       "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
333                       "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
334                       "slave@01:00/01:01:00:06/sbefifo2-dev0/occ-hwmon.2"),
335                   util::CalloutType::fsi);
336     }
337 
338     // FSI-I2C
339     {
340         EXPECT_EQ(util::getCalloutType(
341                       "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
342                       "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
343                       "slave@01:00/01:01:00:03/i2c-211/211-0055"),
344                   util::CalloutType::fsii2c);
345     }
346 
347     // FSI-SPI
348     {
349         EXPECT_EQ(
350             util::getCalloutType(
351                 "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/fsi-master/"
352                 "fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/slave@08:00/"
353                 "01:03:00:04/spi_master/spi9/spi9.0/spi9.00/nvmem"),
354             util::CalloutType::fsispi);
355     }
356 }
357 
358 // Test getting I2C search keys
359 TEST_F(DeviceCalloutsTest, getI2CSearchKeysTest)
360 {
361     {
362         EXPECT_EQ(util::getI2CSearchKeys(
363                       "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/"
364                       "1e78a340.i2c-bus/i2c-10/10-0022"),
365                   (std::tuple{10, 0x22}));
366 
367         EXPECT_EQ(util::getI2CSearchKeys(
368                       "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
369                       "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
370                       "slave@01:00/01:01:00:03/i2c-211/211-0055"),
371                   (std::tuple{11, 0x55}));
372     }
373 
374     {
375         EXPECT_THROW(util::getI2CSearchKeys("/sys/some/bad/path"),
376                      std::invalid_argument);
377     }
378 }
379 //
380 // Test getting SPI search keys
381 TEST_F(DeviceCalloutsTest, getSPISearchKeysTest)
382 {
383     {
384         EXPECT_EQ(
385             util::getSPISearchKeys(
386                 "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/fsi-master/"
387                 "fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/slave@08:00/"
388                 "01:03:00:04/spi_master/spi9/spi9.0/spi9.00/nvmem"),
389             9);
390     }
391 
392     {
393         EXPECT_THROW(util::getSPISearchKeys("/sys/some/bad/path"),
394                      std::invalid_argument);
395     }
396 }
397 
398 // Test getting FSI search keys
399 TEST_F(DeviceCalloutsTest, getFSISearchKeysTest)
400 {
401     {
402         EXPECT_EQ(util::getFSISearchKeys(
403                       "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
404                       "fsi-master/fsi0/slave@00:00/00:00:00:04/spi_master/spi2/"
405                       "spi2.0/spi2.00/nvmem"),
406                   "0");
407     }
408 
409     {
410         EXPECT_EQ(util::getFSISearchKeys(
411                       "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
412                       "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
413                       "slave@01:00/01:01:00:06/sbefifo2-dev0/occ-hwmon.2"),
414                   "0-1");
415     }
416 
417     {
418         EXPECT_EQ(
419             util::getFSISearchKeys(
420                 "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
421                 "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
422                 "slave@01:00/01:01:00:0a:/fsi-master/slave@04:00/01:01:00:0a"),
423             "0-1-4");
424     }
425 
426     {
427         EXPECT_THROW(util::getFSISearchKeys("/sys/some/bad/path"),
428                      std::invalid_argument);
429     }
430 }
431 
432 // Test getting FSI-I2C search keys
433 TEST_F(DeviceCalloutsTest, getFSII2CSearchKeysTest)
434 {
435     {
436         // Link 0-1 bus 11 address 0x55
437         EXPECT_EQ(util::getFSII2CSearchKeys(
438                       "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
439                       "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
440                       "slave@01:00/01:01:00:03/i2c-211/211-0055"),
441                   (std::tuple{"0-1", std::tuple{11, 0x55}}));
442     }
443 
444     {
445         EXPECT_THROW(util::getFSII2CSearchKeys("/sys/some/bad/path"),
446                      std::invalid_argument);
447     }
448 }
449 
450 // Test getting FSI-SPI search keys
451 TEST_F(DeviceCalloutsTest, getFSISPISearchKeysTest)
452 {
453     {
454         // Link 0-8 SPI 9
455         EXPECT_EQ(
456             util::getFSISPISearchKeys(
457                 "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/fsi-master/"
458                 "fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/slave@08:00/"
459                 "01:03:00:04/spi_master/spi9/spi9.0/spi9.00/nvmem"),
460             (std::tuple{"0-8", 9}));
461     }
462 
463     {
464         EXPECT_THROW(util::getFSISPISearchKeys("/sys/some/bad/path"),
465                      std::invalid_argument);
466     }
467 }
468 
469 TEST_F(DeviceCalloutsTest, getCalloutsTest)
470 {
471     std::vector<std::string> systemTypes{"systemA", "systemB"};
472 
473     // A really bogus path
474     {
475         EXPECT_THROW(getCallouts("/bad/path", systemTypes),
476                      std::invalid_argument);
477     }
478 
479     // I2C
480     {
481         auto callouts = getCallouts(
482             "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/"
483             "1e78a340.i2c-bus/i2c-14/14-0072",
484             systemTypes);
485 
486         std::vector<Callout> expected{
487             {"H", "P1-C19", "/chassis/motherboard/cpu0", "core0",
488              "I2C: bus: 14 address: 114 dest: proc-0 target"},
489             {"M", "P1", "/chassis/motherboard", "", ""}};
490 
491         EXPECT_EQ(callouts, expected);
492 
493         // Use the bus/address API instead of the device path one
494         callouts = getI2CCallouts(14, 0x72, systemTypes);
495         EXPECT_EQ(callouts, expected);
496 
497         // I2C address not in JSON
498         EXPECT_THROW(
499             getCallouts(
500                 "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/"
501                 "1e78a340.i2c-bus/i2c-14/14-0099",
502                 systemTypes),
503             std::invalid_argument);
504 
505         // A bad JSON entry, missing the location code
506         EXPECT_THROW(
507             getCallouts(
508                 "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/"
509                 "1e78a340.i2c-bus/i2c-0/0-005a",
510                 systemTypes),
511             std::runtime_error);
512     }
513 
514     // FSI
515     {
516         auto callouts = getCallouts(
517             "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
518             "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
519             "slave@01:00/01:01:00:06/sbefifo2-dev0/occ-hwmon.2",
520             systemTypes);
521 
522         std::vector<Callout> expected{{"H", "P1-C19",
523                                        "/chassis/motherboard/cpu0", "core",
524                                        "FSI: links: 0-1 dest: proc-0 target"}};
525 
526         EXPECT_EQ(callouts, expected);
527 
528         // link 9-1 not in JSON
529         EXPECT_THROW(
530             getCallouts(
531                 "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
532                 "fsi-master/fsi0/slave@09:00/00:00:00:0a/fsi-master/fsi1/"
533                 "slave@01:00/01:01:00:06/sbefifo2-dev0/occ-hwmon.2",
534                 systemTypes),
535             std::invalid_argument);
536     }
537 
538     // FSI-I2C
539     {
540         auto callouts = getCallouts(
541             "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
542             "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
543             "slave@03:00/01:01:00:03/i2c-207/207-0019",
544             systemTypes);
545 
546         std::vector<Callout> expected{
547             {"H", "P1-C25", "/chassis/motherboard/cpu5", "",
548              "FSI-I2C: links: 0-3 bus: 7 addr: 25 dest: proc-5 target"},
549             {"M", "P1", "/chassis/motherboard", "", ""},
550             {"L", "P2", "/chassis/motherboard/bmc", "", ""}};
551 
552         EXPECT_EQ(callouts, expected);
553 
554         // Bus 2 not in JSON
555         EXPECT_THROW(
556             getCallouts(
557                 "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
558                 "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
559                 "slave@03:00/01:01:00:03/i2c-202/202-0019",
560                 systemTypes),
561             std::invalid_argument);
562     }
563 
564     // FSI-SPI
565     {
566         auto callouts =
567             getCallouts("/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
568                         "fsi-master/fsi0/slave@08:00/00:00:00:04/spi_master/"
569                         "spi3/spi3.0/spi3.00/nvmem",
570                         systemTypes);
571 
572         std::vector<Callout> expected{
573             {"H", "P1-C19", "/chassis/motherboard/cpu0", "",
574              "FSI-SPI: links: 8 bus: 3 dest: proc-0 target"}};
575 
576         EXPECT_EQ(callouts, expected);
577 
578         // Bus 7 not in the JSON
579         EXPECT_THROW(
580             getCallouts("/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
581                         "fsi-master/fsi0/slave@08:00/00:00:00:04/spi_master/"
582                         "spi7/spi7.0/spi7.00/nvmem",
583                         systemTypes),
584             std::invalid_argument);
585     }
586 }
587