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 #include "action.hpp"
17 #include "chassis.hpp"
18 #include "configuration.hpp"
19 #include "device.hpp"
20 #include "i2c_interface.hpp"
21 #include "i2c_write_byte_action.hpp"
22 #include "mock_action.hpp"
23 #include "mock_error_logging.hpp"
24 #include "mock_journal.hpp"
25 #include "mock_services.hpp"
26 #include "mocked_i2c_interface.hpp"
27 #include "phase_fault_detection.hpp"
28 #include "pmbus_utils.hpp"
29 #include "pmbus_write_vout_command_action.hpp"
30 #include "presence_detection.hpp"
31 #include "rail.hpp"
32 #include "rule.hpp"
33 #include "system.hpp"
34 
35 #include <cstdint>
36 #include <memory>
37 #include <optional>
38 #include <utility>
39 #include <vector>
40 
41 #include <gmock/gmock.h>
42 #include <gtest/gtest.h>
43 
44 using namespace phosphor::power::regulators;
45 using namespace phosphor::power::regulators::pmbus_utils;
46 
47 using ::testing::A;
48 using ::testing::Ref;
49 using ::testing::Return;
50 using ::testing::Throw;
51 using ::testing::TypedEq;
52 
53 static const std::string chassisInvPath{
54     "/xyz/openbmc_project/inventory/system/chassis"};
55 
56 TEST(ConfigurationTests, Constructor)
57 {
58     // Test where volts value specified
59     {
60         std::optional<double> volts{1.3};
61 
62         std::vector<std::unique_ptr<Action>> actions{};
63         actions.push_back(std::make_unique<MockAction>());
64         actions.push_back(std::make_unique<MockAction>());
65 
66         Configuration configuration(volts, std::move(actions));
67         EXPECT_EQ(configuration.getVolts().has_value(), true);
68         EXPECT_EQ(configuration.getVolts().value(), 1.3);
69         EXPECT_EQ(configuration.getActions().size(), 2);
70     }
71 
72     // Test where volts value not specified
73     {
74         std::optional<double> volts{};
75 
76         std::vector<std::unique_ptr<Action>> actions{};
77         actions.push_back(std::make_unique<MockAction>());
78 
79         Configuration configuration(volts, std::move(actions));
80         EXPECT_EQ(configuration.getVolts().has_value(), false);
81         EXPECT_EQ(configuration.getActions().size(), 1);
82     }
83 }
84 
85 // Test for execute(Services&, System&, Chassis&, Device&)
86 TEST(ConfigurationTests, ExecuteForDevice)
87 {
88     // Test where works: Volts value not specified
89     {
90         // Create mock services.  Expect logDebug() to be called.
91         MockServices services{};
92         MockJournal& journal = services.getMockJournal();
93         EXPECT_CALL(journal, logDebug("Configuring vdd_reg")).Times(1);
94         EXPECT_CALL(journal, logError(A<const std::string&>())).Times(0);
95 
96         // Create I2CWriteByteAction with register 0x7C and value 0x0A
97         std::unique_ptr<I2CWriteByteAction> action =
98             std::make_unique<I2CWriteByteAction>(0x7C, 0x0A);
99 
100         // Create mock I2CInterface.  Expect action to write 0x0A to 0x7C.
101         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
102             std::make_unique<i2c::MockedI2CInterface>();
103         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
104         EXPECT_CALL(*i2cInterface,
105                     write(TypedEq<uint8_t>(0x7C), TypedEq<uint8_t>(0x0A)))
106             .Times(1);
107 
108         // Create Configuration with no volts value specified
109         std::optional<double> volts{};
110         std::vector<std::unique_ptr<Action>> actions{};
111         actions.emplace_back(std::move(action));
112         std::unique_ptr<Configuration> configuration =
113             std::make_unique<Configuration>(volts, std::move(actions));
114         Configuration* configurationPtr = configuration.get();
115 
116         // Create Device that contains Configuration
117         std::unique_ptr<PresenceDetection> presenceDetection{};
118         std::unique_ptr<Device> device = std::make_unique<Device>(
119             "vdd_reg", true,
120             "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg2",
121             std::move(i2cInterface), std::move(presenceDetection),
122             std::move(configuration));
123         Device* devicePtr = device.get();
124 
125         // Create Chassis that contains Device
126         std::vector<std::unique_ptr<Device>> devices{};
127         devices.emplace_back(std::move(device));
128         std::unique_ptr<Chassis> chassis =
129             std::make_unique<Chassis>(1, chassisInvPath, std::move(devices));
130         Chassis* chassisPtr = chassis.get();
131 
132         // Create System that contains Chassis
133         std::vector<std::unique_ptr<Rule>> rules{};
134         std::vector<std::unique_ptr<Chassis>> chassisVec{};
135         chassisVec.emplace_back(std::move(chassis));
136         System system{std::move(rules), std::move(chassisVec)};
137 
138         // Execute Configuration
139         configurationPtr->execute(services, system, *chassisPtr, *devicePtr);
140     }
141 
142     // Test where works: Volts value specified
143     {
144         // Create mock services.  Expect logDebug() to be called.
145         MockServices services{};
146         MockJournal& journal = services.getMockJournal();
147         EXPECT_CALL(journal, logDebug("Configuring vdd_reg: volts=1.300000"))
148             .Times(1);
149         EXPECT_CALL(journal, logError(A<const std::string&>())).Times(0);
150 
151         // Create PMBusWriteVoutCommandAction.  Do not specify a volts value
152         // because it will get a value of 1.3V from the
153         // ActionEnvironment/Configuration.  Specify a -8 exponent.
154         // Linear format volts value = (1.3 / 2^(-8)) = 332.8 = 333 = 0x014D.
155         std::optional<double> volts{};
156         std::unique_ptr<PMBusWriteVoutCommandAction> action =
157             std::make_unique<PMBusWriteVoutCommandAction>(
158                 volts, pmbus_utils::VoutDataFormat::linear, -8, false);
159 
160         // Create mock I2CInterface.  Expect action to write 0x014D to
161         // VOUT_COMMAND (command/register 0x21).
162         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
163             std::make_unique<i2c::MockedI2CInterface>();
164         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
165         EXPECT_CALL(*i2cInterface,
166                     write(TypedEq<uint8_t>(0x21), TypedEq<uint16_t>(0x014D)))
167             .Times(1);
168 
169         // Create Configuration with volts value 1.3V
170         std::vector<std::unique_ptr<Action>> actions{};
171         actions.emplace_back(std::move(action));
172         std::unique_ptr<Configuration> configuration =
173             std::make_unique<Configuration>(1.3, std::move(actions));
174         Configuration* configurationPtr = configuration.get();
175 
176         // Create Device that contains Configuration
177         std::unique_ptr<PresenceDetection> presenceDetection{};
178         std::unique_ptr<Device> device = std::make_unique<Device>(
179             "vdd_reg", true,
180             "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg2",
181             std::move(i2cInterface), std::move(presenceDetection),
182             std::move(configuration));
183         Device* devicePtr = device.get();
184 
185         // Create Chassis that contains Device
186         std::vector<std::unique_ptr<Device>> devices{};
187         devices.emplace_back(std::move(device));
188         std::unique_ptr<Chassis> chassis =
189             std::make_unique<Chassis>(1, chassisInvPath, std::move(devices));
190         Chassis* chassisPtr = chassis.get();
191 
192         // Create System that contains Chassis
193         std::vector<std::unique_ptr<Rule>> rules{};
194         std::vector<std::unique_ptr<Chassis>> chassisVec{};
195         chassisVec.emplace_back(std::move(chassis));
196         System system{std::move(rules), std::move(chassisVec)};
197 
198         // Execute Configuration
199         configurationPtr->execute(services, system, *chassisPtr, *devicePtr);
200     }
201 
202     // Test where fails
203     {
204         // Create mock services.  Expect logDebug(), logError(), and
205         // logI2CError() to be called.
206         MockServices services{};
207         MockErrorLogging& errorLogging = services.getMockErrorLogging();
208         MockJournal& journal = services.getMockJournal();
209         std::vector<std::string> expectedErrMessagesException{
210             "I2CException: Failed to write byte: bus /dev/i2c-1, addr 0x70",
211             "ActionError: i2c_write_byte: { register: 0x7C, value: 0xA, mask: "
212             "0xFF }"};
213         EXPECT_CALL(journal, logDebug("Configuring vdd_reg")).Times(1);
214         EXPECT_CALL(journal, logError(expectedErrMessagesException)).Times(1);
215         EXPECT_CALL(journal, logError("Unable to configure vdd_reg")).Times(1);
216         EXPECT_CALL(errorLogging,
217                     logI2CError(Entry::Level::Warning, Ref(journal),
218                                 "/dev/i2c-1", 0x70, 0))
219             .Times(1);
220 
221         // Create I2CWriteByteAction with register 0x7C and value 0x0A
222         std::unique_ptr<I2CWriteByteAction> action =
223             std::make_unique<I2CWriteByteAction>(0x7C, 0x0A);
224 
225         // Create mock I2CInterface.  write() throws an I2CException.
226         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
227             std::make_unique<i2c::MockedI2CInterface>();
228         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
229         EXPECT_CALL(*i2cInterface,
230                     write(TypedEq<uint8_t>(0x7C), TypedEq<uint8_t>(0x0A)))
231             .Times(1)
232             .WillOnce(Throw(
233                 i2c::I2CException{"Failed to write byte", "/dev/i2c-1", 0x70}));
234 
235         // Create Configuration with no volts value specified
236         std::optional<double> volts{};
237         std::vector<std::unique_ptr<Action>> actions{};
238         actions.emplace_back(std::move(action));
239         std::unique_ptr<Configuration> configuration =
240             std::make_unique<Configuration>(volts, std::move(actions));
241         Configuration* configurationPtr = configuration.get();
242 
243         // Create Device that contains Configuration
244         std::unique_ptr<PresenceDetection> presenceDetection{};
245         std::unique_ptr<Device> device = std::make_unique<Device>(
246             "vdd_reg", true,
247             "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg2",
248             std::move(i2cInterface), std::move(presenceDetection),
249             std::move(configuration));
250         Device* devicePtr = device.get();
251 
252         // Create Chassis that contains Device
253         std::vector<std::unique_ptr<Device>> devices{};
254         devices.emplace_back(std::move(device));
255         std::unique_ptr<Chassis> chassis =
256             std::make_unique<Chassis>(1, chassisInvPath, std::move(devices));
257         Chassis* chassisPtr = chassis.get();
258 
259         // Create System that contains Chassis
260         std::vector<std::unique_ptr<Rule>> rules{};
261         std::vector<std::unique_ptr<Chassis>> chassisVec{};
262         chassisVec.emplace_back(std::move(chassis));
263         System system{std::move(rules), std::move(chassisVec)};
264 
265         // Execute Configuration
266         configurationPtr->execute(services, system, *chassisPtr, *devicePtr);
267     }
268 }
269 
270 // Test for execute(Services&, System&, Chassis&, Device&, Rail&)
271 TEST(ConfigurationTests, ExecuteForRail)
272 {
273     // Test where works: Volts value not specified
274     {
275         // Create mock services.  Expect logDebug() to be called.
276         MockServices services{};
277         MockJournal& journal = services.getMockJournal();
278         EXPECT_CALL(journal, logDebug("Configuring vio2")).Times(1);
279         EXPECT_CALL(journal, logError(A<const std::string&>())).Times(0);
280 
281         // Create I2CWriteByteAction with register 0x7C and value 0x0A
282         std::unique_ptr<I2CWriteByteAction> action =
283             std::make_unique<I2CWriteByteAction>(0x7C, 0x0A);
284 
285         // Create mock I2CInterface.  Expect action to write 0x0A to 0x7C.
286         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
287             std::make_unique<i2c::MockedI2CInterface>();
288         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
289         EXPECT_CALL(*i2cInterface,
290                     write(TypedEq<uint8_t>(0x7C), TypedEq<uint8_t>(0x0A)))
291             .Times(1);
292 
293         // Create Configuration with no volts value specified
294         std::optional<double> volts{};
295         std::vector<std::unique_ptr<Action>> actions{};
296         actions.emplace_back(std::move(action));
297         std::unique_ptr<Configuration> configuration =
298             std::make_unique<Configuration>(volts, std::move(actions));
299         Configuration* configurationPtr = configuration.get();
300 
301         // Create Rail that contains Configuration
302         std::unique_ptr<Rail> rail =
303             std::make_unique<Rail>("vio2", std::move(configuration));
304         Rail* railPtr = rail.get();
305 
306         // Create Device that contains Rail
307         std::unique_ptr<PresenceDetection> presenceDetection{};
308         std::unique_ptr<Configuration> deviceConfiguration{};
309         std::unique_ptr<PhaseFaultDetection> phaseFaultDetection{};
310         std::vector<std::unique_ptr<Rail>> rails{};
311         rails.emplace_back(std::move(rail));
312         std::unique_ptr<Device> device = std::make_unique<Device>(
313             "reg1", true,
314             "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg1",
315             std::move(i2cInterface), std::move(presenceDetection),
316             std::move(deviceConfiguration), std::move(phaseFaultDetection),
317             std::move(rails));
318         Device* devicePtr = device.get();
319 
320         // Create Chassis that contains Device
321         std::vector<std::unique_ptr<Device>> devices{};
322         devices.emplace_back(std::move(device));
323         std::unique_ptr<Chassis> chassis =
324             std::make_unique<Chassis>(1, chassisInvPath, std::move(devices));
325         Chassis* chassisPtr = chassis.get();
326 
327         // Create System that contains Chassis
328         std::vector<std::unique_ptr<Rule>> rules{};
329         std::vector<std::unique_ptr<Chassis>> chassisVec{};
330         chassisVec.emplace_back(std::move(chassis));
331         System system{std::move(rules), std::move(chassisVec)};
332 
333         // Execute Configuration
334         configurationPtr->execute(services, system, *chassisPtr, *devicePtr,
335                                   *railPtr);
336     }
337 
338     // Test where works: Volts value specified
339     {
340         // Create mock services.  Expect logDebug() to be called.
341         MockServices services{};
342         MockJournal& journal = services.getMockJournal();
343         EXPECT_CALL(journal, logDebug("Configuring vio2: volts=1.300000"))
344             .Times(1);
345         EXPECT_CALL(journal, logError(A<const std::string&>())).Times(0);
346 
347         // Create PMBusWriteVoutCommandAction.  Do not specify a volts value
348         // because it will get a value of 1.3V from the
349         // ActionEnvironment/Configuration.  Specify a -8 exponent.
350         // Linear format volts value = (1.3 / 2^(-8)) = 332.8 = 333 = 0x014D.
351         std::optional<double> volts{};
352         std::unique_ptr<PMBusWriteVoutCommandAction> action =
353             std::make_unique<PMBusWriteVoutCommandAction>(
354                 volts, pmbus_utils::VoutDataFormat::linear, -8, false);
355 
356         // Create mock I2CInterface.  Expect action to write 0x014D to
357         // VOUT_COMMAND (command/register 0x21).
358         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
359             std::make_unique<i2c::MockedI2CInterface>();
360         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
361         EXPECT_CALL(*i2cInterface,
362                     write(TypedEq<uint8_t>(0x21), TypedEq<uint16_t>(0x014D)))
363             .Times(1);
364 
365         // Create Configuration with volts value 1.3V
366         std::vector<std::unique_ptr<Action>> actions{};
367         actions.emplace_back(std::move(action));
368         std::unique_ptr<Configuration> configuration =
369             std::make_unique<Configuration>(1.3, std::move(actions));
370         Configuration* configurationPtr = configuration.get();
371 
372         // Create Rail that contains Configuration
373         std::unique_ptr<Rail> rail =
374             std::make_unique<Rail>("vio2", std::move(configuration));
375         Rail* railPtr = rail.get();
376 
377         // Create Device that contains Rail
378         std::unique_ptr<PresenceDetection> presenceDetection{};
379         std::unique_ptr<Configuration> deviceConfiguration{};
380         std::unique_ptr<PhaseFaultDetection> phaseFaultDetection{};
381         std::vector<std::unique_ptr<Rail>> rails{};
382         rails.emplace_back(std::move(rail));
383         std::unique_ptr<Device> device = std::make_unique<Device>(
384             "reg1", true,
385             "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg1",
386             std::move(i2cInterface), std::move(presenceDetection),
387             std::move(deviceConfiguration), std::move(phaseFaultDetection),
388             std::move(rails));
389         Device* devicePtr = device.get();
390 
391         // Create Chassis that contains Device
392         std::vector<std::unique_ptr<Device>> devices{};
393         devices.emplace_back(std::move(device));
394         std::unique_ptr<Chassis> chassis =
395             std::make_unique<Chassis>(1, chassisInvPath, std::move(devices));
396         Chassis* chassisPtr = chassis.get();
397 
398         // Create System that contains Chassis
399         std::vector<std::unique_ptr<Rule>> rules{};
400         std::vector<std::unique_ptr<Chassis>> chassisVec{};
401         chassisVec.emplace_back(std::move(chassis));
402         System system{std::move(rules), std::move(chassisVec)};
403 
404         // Execute Configuration
405         configurationPtr->execute(services, system, *chassisPtr, *devicePtr,
406                                   *railPtr);
407     }
408 
409     // Test where fails
410     {
411         // Create mock services.  Expect logDebug(), logError(), and logI2CError
412         // to be called.
413         MockServices services{};
414         MockErrorLogging& errorLogging = services.getMockErrorLogging();
415         MockJournal& journal = services.getMockJournal();
416         std::vector<std::string> expectedErrMessagesException{
417             "I2CException: Failed to write byte: bus /dev/i2c-1, addr 0x70",
418             "ActionError: i2c_write_byte: { register: 0x7C, value: 0xA, mask: "
419             "0xFF }"};
420         EXPECT_CALL(journal, logDebug("Configuring vio2")).Times(1);
421         EXPECT_CALL(journal, logError(expectedErrMessagesException)).Times(1);
422         EXPECT_CALL(journal, logError("Unable to configure vio2")).Times(1);
423         EXPECT_CALL(errorLogging,
424                     logI2CError(Entry::Level::Warning, Ref(journal),
425                                 "/dev/i2c-1", 0x70, 0))
426             .Times(1);
427 
428         // Create I2CWriteByteAction with register 0x7C and value 0x0A
429         std::unique_ptr<I2CWriteByteAction> action =
430             std::make_unique<I2CWriteByteAction>(0x7C, 0x0A);
431 
432         // Create mock I2CInterface.  write() throws an I2CException.
433         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
434             std::make_unique<i2c::MockedI2CInterface>();
435         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
436         EXPECT_CALL(*i2cInterface,
437                     write(TypedEq<uint8_t>(0x7C), TypedEq<uint8_t>(0x0A)))
438             .Times(1)
439             .WillOnce(Throw(
440                 i2c::I2CException{"Failed to write byte", "/dev/i2c-1", 0x70}));
441 
442         // Create Configuration with no volts value specified
443         std::optional<double> volts{};
444         std::vector<std::unique_ptr<Action>> actions{};
445         actions.emplace_back(std::move(action));
446         std::unique_ptr<Configuration> configuration =
447             std::make_unique<Configuration>(volts, std::move(actions));
448         Configuration* configurationPtr = configuration.get();
449 
450         // Create Rail that contains Configuration
451         std::unique_ptr<Rail> rail =
452             std::make_unique<Rail>("vio2", std::move(configuration));
453         Rail* railPtr = rail.get();
454 
455         // Create Device that contains Rail
456         std::unique_ptr<PresenceDetection> presenceDetection{};
457         std::unique_ptr<Configuration> deviceConfiguration{};
458         std::unique_ptr<PhaseFaultDetection> phaseFaultDetection{};
459         std::vector<std::unique_ptr<Rail>> rails{};
460         rails.emplace_back(std::move(rail));
461         std::unique_ptr<Device> device = std::make_unique<Device>(
462             "reg1", true,
463             "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg1",
464             std::move(i2cInterface), std::move(presenceDetection),
465             std::move(deviceConfiguration), std::move(phaseFaultDetection),
466             std::move(rails));
467         Device* devicePtr = device.get();
468 
469         // Create Chassis that contains Device
470         std::vector<std::unique_ptr<Device>> devices{};
471         devices.emplace_back(std::move(device));
472         std::unique_ptr<Chassis> chassis =
473             std::make_unique<Chassis>(1, chassisInvPath, std::move(devices));
474         Chassis* chassisPtr = chassis.get();
475 
476         // Create System that contains Chassis
477         std::vector<std::unique_ptr<Rule>> rules{};
478         std::vector<std::unique_ptr<Chassis>> chassisVec{};
479         chassisVec.emplace_back(std::move(chassis));
480         System system{std::move(rules), std::move(chassisVec)};
481 
482         // Execute Configuration
483         configurationPtr->execute(services, system, *chassisPtr, *devicePtr,
484                                   *railPtr);
485     }
486 }
487 
488 TEST(ConfigurationTests, GetActions)
489 {
490     std::optional<double> volts{1.3};
491 
492     std::vector<std::unique_ptr<Action>> actions{};
493 
494     MockAction* action1 = new MockAction{};
495     actions.push_back(std::unique_ptr<MockAction>{action1});
496 
497     MockAction* action2 = new MockAction{};
498     actions.push_back(std::unique_ptr<MockAction>{action2});
499 
500     Configuration configuration(volts, std::move(actions));
501     EXPECT_EQ(configuration.getActions().size(), 2);
502     EXPECT_EQ(configuration.getActions()[0].get(), action1);
503     EXPECT_EQ(configuration.getActions()[1].get(), action2);
504 }
505 
506 TEST(ConfigurationTests, GetVolts)
507 {
508     // Test where volts value specified
509     {
510         std::optional<double> volts{3.2};
511 
512         std::vector<std::unique_ptr<Action>> actions{};
513         actions.push_back(std::make_unique<MockAction>());
514 
515         Configuration configuration(volts, std::move(actions));
516         EXPECT_EQ(configuration.getVolts().has_value(), true);
517         EXPECT_EQ(configuration.getVolts().value(), 3.2);
518     }
519 
520     // Test where volts value not specified
521     {
522         std::optional<double> volts{};
523 
524         std::vector<std::unique_ptr<Action>> actions{};
525         actions.push_back(std::make_unique<MockAction>());
526 
527         Configuration configuration(volts, std::move(actions));
528         EXPECT_EQ(configuration.getVolts().has_value(), false);
529     }
530 }
531