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 
316     // Invalid
317     {
318         EXPECT_EQ(util::getCalloutType("/some/bad/device/path"),
319                   util::CalloutType::unknown);
320     }
321 
322     // I2C
323     {
324         EXPECT_EQ(util::getCalloutType(
325                       "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/"
326                       "1e78a340.i2c-bus/i2c-14/14-0072"),
327                   util::CalloutType::i2c);
328     }
329 
330     // FSI
331     {
332         EXPECT_EQ(util::getCalloutType(
333                       "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
334                       "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
335                       "slave@01:00/01:01:00:06/sbefifo2-dev0/occ-hwmon.2"),
336                   util::CalloutType::fsi);
337     }
338 
339     // FSI-I2C
340     {
341         EXPECT_EQ(util::getCalloutType(
342                       "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
343                       "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
344                       "slave@01:00/01:01:00:03/i2c-211/211-0055"),
345                   util::CalloutType::fsii2c);
346     }
347 
348     // FSI-SPI
349     {
350 
351         EXPECT_EQ(
352             util::getCalloutType(
353                 "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/fsi-master/"
354                 "fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/slave@08:00/"
355                 "01:03:00:04/spi_master/spi9/spi9.0/spi9.00/nvmem"),
356             util::CalloutType::fsispi);
357     }
358 }
359 
360 // Test getting I2C search keys
361 TEST_F(DeviceCalloutsTest, getI2CSearchKeysTest)
362 {
363 
364     {
365         EXPECT_EQ(util::getI2CSearchKeys(
366                       "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/"
367                       "1e78a340.i2c-bus/i2c-10/10-0022"),
368                   (std::tuple{10, 0x22}));
369 
370         EXPECT_EQ(util::getI2CSearchKeys(
371                       "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
372                       "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
373                       "slave@01:00/01:01:00:03/i2c-211/211-0055"),
374                   (std::tuple{11, 0x55}));
375     }
376 
377     {
378         EXPECT_THROW(util::getI2CSearchKeys("/sys/some/bad/path"),
379                      std::invalid_argument);
380     }
381 }
382 //
383 // Test getting SPI search keys
384 TEST_F(DeviceCalloutsTest, getSPISearchKeysTest)
385 {
386     {
387         EXPECT_EQ(
388             util::getSPISearchKeys(
389                 "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/fsi-master/"
390                 "fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/slave@08:00/"
391                 "01:03:00:04/spi_master/spi9/spi9.0/spi9.00/nvmem"),
392             9);
393     }
394 
395     {
396         EXPECT_THROW(util::getSPISearchKeys("/sys/some/bad/path"),
397                      std::invalid_argument);
398     }
399 }
400 
401 // Test getting FSI search keys
402 TEST_F(DeviceCalloutsTest, getFSISearchKeysTest)
403 {
404     {
405         EXPECT_EQ(util::getFSISearchKeys(
406                       "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
407                       "fsi-master/fsi0/slave@00:00/00:00:00:04/spi_master/spi2/"
408                       "spi2.0/spi2.00/nvmem"),
409                   "0");
410     }
411 
412     {
413         EXPECT_EQ(util::getFSISearchKeys(
414                       "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
415                       "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
416                       "slave@01:00/01:01:00:06/sbefifo2-dev0/occ-hwmon.2"),
417                   "0-1");
418     }
419 
420     {
421         EXPECT_EQ(
422             util::getFSISearchKeys(
423                 "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
424                 "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
425                 "slave@01:00/01:01:00:0a:/fsi-master/slave@04:00/01:01:00:0a"),
426             "0-1-4");
427     }
428 
429     {
430         EXPECT_THROW(util::getFSISearchKeys("/sys/some/bad/path"),
431                      std::invalid_argument);
432     }
433 }
434 
435 // Test getting FSI-I2C search keys
436 TEST_F(DeviceCalloutsTest, getFSII2CSearchKeysTest)
437 {
438     {
439         // Link 0-1 bus 11 address 0x55
440         EXPECT_EQ(util::getFSII2CSearchKeys(
441                       "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
442                       "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
443                       "slave@01:00/01:01:00:03/i2c-211/211-0055"),
444                   (std::tuple{"0-1", std::tuple{11, 0x55}}));
445     }
446 
447     {
448         EXPECT_THROW(util::getFSII2CSearchKeys("/sys/some/bad/path"),
449                      std::invalid_argument);
450     }
451 }
452 
453 // Test getting FSI-SPI search keys
454 TEST_F(DeviceCalloutsTest, getFSISPISearchKeysTest)
455 {
456     {
457         // Link 0-8 SPI 9
458         EXPECT_EQ(
459             util::getFSISPISearchKeys(
460                 "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/fsi-master/"
461                 "fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/slave@08:00/"
462                 "01:03:00:04/spi_master/spi9/spi9.0/spi9.00/nvmem"),
463             (std::tuple{"0-8", 9}));
464     }
465 
466     {
467         EXPECT_THROW(util::getFSISPISearchKeys("/sys/some/bad/path"),
468                      std::invalid_argument);
469     }
470 }
471 
472 TEST_F(DeviceCalloutsTest, getCalloutsTest)
473 {
474     std::vector<std::string> systemTypes{"systemA", "systemB"};
475 
476     // A really bogus path
477     {
478         EXPECT_THROW(getCallouts("/bad/path", systemTypes),
479                      std::invalid_argument);
480     }
481 
482     // I2C
483     {
484         auto callouts = getCallouts(
485             "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/"
486             "1e78a340.i2c-bus/i2c-14/14-0072",
487             systemTypes);
488 
489         std::vector<Callout> expected{
490             {"H", "P1-C19", "/chassis/motherboard/cpu0", "core0",
491              "I2C: bus: 14 address: 114 dest: proc-0 target"},
492             {"M", "P1", "/chassis/motherboard", "", ""}};
493 
494         EXPECT_EQ(callouts, expected);
495 
496         // Use the bus/address API instead of the device path one
497         callouts = getI2CCallouts(14, 0x72, systemTypes);
498         EXPECT_EQ(callouts, expected);
499 
500         // I2C address not in JSON
501         EXPECT_THROW(
502             getCallouts(
503                 "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/"
504                 "1e78a340.i2c-bus/i2c-14/14-0099",
505                 systemTypes),
506             std::invalid_argument);
507 
508         // A bad JSON entry, missing the location code
509         EXPECT_THROW(
510             getCallouts(
511                 "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/"
512                 "1e78a340.i2c-bus/i2c-0/0-005a",
513                 systemTypes),
514             std::runtime_error);
515     }
516 
517     // FSI
518     {
519         auto callouts = getCallouts(
520             "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
521             "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
522             "slave@01:00/01:01:00:06/sbefifo2-dev0/occ-hwmon.2",
523             systemTypes);
524 
525         std::vector<Callout> expected{{"H", "P1-C19",
526                                        "/chassis/motherboard/cpu0", "core",
527                                        "FSI: links: 0-1 dest: proc-0 target"}};
528 
529         EXPECT_EQ(callouts, expected);
530 
531         // link 9-1 not in JSON
532         EXPECT_THROW(
533             getCallouts(
534                 "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
535                 "fsi-master/fsi0/slave@09:00/00:00:00:0a/fsi-master/fsi1/"
536                 "slave@01:00/01:01:00:06/sbefifo2-dev0/occ-hwmon.2",
537                 systemTypes),
538             std::invalid_argument);
539     }
540 
541     // FSI-I2C
542     {
543         auto callouts = getCallouts(
544             "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
545             "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
546             "slave@03:00/01:01:00:03/i2c-207/207-0019",
547             systemTypes);
548 
549         std::vector<Callout> expected{
550             {"H", "P1-C25", "/chassis/motherboard/cpu5", "",
551              "FSI-I2C: links: 0-3 bus: 7 addr: 25 dest: proc-5 target"},
552             {"M", "P1", "/chassis/motherboard", "", ""},
553             {"L", "P2", "/chassis/motherboard/bmc", "", ""}};
554 
555         EXPECT_EQ(callouts, expected);
556 
557         // Bus 2 not in JSON
558         EXPECT_THROW(
559             getCallouts(
560                 "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
561                 "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
562                 "slave@03:00/01:01:00:03/i2c-202/202-0019",
563                 systemTypes),
564             std::invalid_argument);
565     }
566 
567     // FSI-SPI
568     {
569         auto callouts =
570             getCallouts("/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
571                         "fsi-master/fsi0/slave@08:00/00:00:00:04/spi_master/"
572                         "spi3/spi3.0/spi3.00/nvmem",
573                         systemTypes);
574 
575         std::vector<Callout> expected{
576             {"H", "P1-C19", "/chassis/motherboard/cpu0", "",
577              "FSI-SPI: links: 8 bus: 3 dest: proc-0 target"}};
578 
579         EXPECT_EQ(callouts, expected);
580 
581         // Bus 7 not in the JSON
582         EXPECT_THROW(
583             getCallouts("/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
584                         "fsi-master/fsi0/slave@08:00/00:00:00:04/spi_master/"
585                         "spi7/spi7.0/spi7.00/nvmem",
586                         systemTypes),
587             std::invalid_argument);
588     }
589 }
590