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