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_environment.hpp"
17 #include "action_error.hpp"
18 #include "device.hpp"
19 #include "i2c_interface.hpp"
20 #include "id_map.hpp"
21 #include "mock_services.hpp"
22 #include "mocked_i2c_interface.hpp"
23 #include "pmbus_error.hpp"
24 #include "pmbus_read_sensor_action.hpp"
25 #include "pmbus_utils.hpp"
26 #include "sensors.hpp"
27 #include "test_sdbus_error.hpp"
28 
29 #include <cstdint>
30 #include <exception>
31 #include <memory>
32 #include <optional>
33 #include <stdexcept>
34 #include <string>
35 #include <utility>
36 
37 #include <gmock/gmock.h>
38 #include <gtest/gtest.h>
39 
40 using namespace phosphor::power::regulators;
41 using namespace phosphor::power::regulators::pmbus_utils;
42 
43 using ::testing::A;
44 using ::testing::Return;
45 using ::testing::SetArgReferee;
46 using ::testing::Throw;
47 using ::testing::TypedEq;
48 
TEST(PMBusReadSensorActionTests,Constructor)49 TEST(PMBusReadSensorActionTests, Constructor)
50 {
51     // Test where works: exponent value is specified
52     {
53         SensorType type{SensorType::vout};
54         uint8_t command{0x8B};
55         SensorDataFormat format{SensorDataFormat::linear_16};
56         std::optional<int8_t> exponent{-8};
57         PMBusReadSensorAction action{type, command, format, exponent};
58         EXPECT_EQ(action.getType(), SensorType::vout);
59         EXPECT_EQ(action.getCommand(), 0x8B);
60         EXPECT_EQ(action.getFormat(), SensorDataFormat::linear_16);
61         EXPECT_EQ(action.getExponent().has_value(), true);
62         EXPECT_EQ(action.getExponent().value(), -8);
63     }
64 
65     // Test where works: exponent value is not specified
66     {
67         SensorType type{SensorType::iout};
68         uint8_t command{0x8C};
69         SensorDataFormat format{SensorDataFormat::linear_11};
70         std::optional<int8_t> exponent{};
71         PMBusReadSensorAction action{type, command, format, exponent};
72         EXPECT_EQ(action.getType(), SensorType::iout);
73         EXPECT_EQ(action.getCommand(), 0x8C);
74         EXPECT_EQ(action.getFormat(), SensorDataFormat::linear_11);
75         EXPECT_EQ(action.getExponent().has_value(), false);
76     }
77 }
78 
TEST(PMBusReadSensorActionTests,Execute)79 TEST(PMBusReadSensorActionTests, Execute)
80 {
81     // Test where works: linear_11 format
82     try
83     {
84         // Determine READ_IOUT linear data value and decimal value
85         // * 5 bit exponent: -6 = 11010
86         // * 11 bit mantissa: 736 = 010 1110 0000
87         // * linear data format = 1101 0010 1110 0000 = 0xD2E0
88         // * Decimal value: 736 * 2^(-6) = 11.5
89 
90         // Create mock I2CInterface.  Expect action to do the following:
91         // * will read 0xD2E0 from READ_IOUT (command/register 0x8C)
92         // * will not read from VOUT_MODE (command/register 0x20)
93         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
94             std::make_unique<i2c::MockedI2CInterface>();
95         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
96         EXPECT_CALL(*i2cInterface, read(TypedEq<uint8_t>(0x8C), A<uint16_t&>()))
97             .Times(1)
98             .WillOnce(SetArgReferee<1>(0xD2E0));
99         EXPECT_CALL(*i2cInterface, read(A<uint8_t>(), A<uint8_t&>())).Times(0);
100 
101         // Create MockServices.  Expect the sensor value to be set.
102         MockServices services{};
103         MockSensors& sensors = services.getMockSensors();
104         EXPECT_CALL(sensors, setValue(SensorType::iout, 11.5)).Times(1);
105 
106         // Create Device, IDMap, and ActionEnvironment
107         Device device{
108             "reg1", true,
109             "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg1",
110             std::move(i2cInterface)};
111         IDMap idMap{};
112         idMap.addDevice(device);
113         ActionEnvironment env{idMap, "reg1", services};
114 
115         // Create and execute action
116         SensorType type{SensorType::iout};
117         uint8_t command{0x8C};
118         SensorDataFormat format{SensorDataFormat::linear_11};
119         std::optional<int8_t> exponent{};
120         PMBusReadSensorAction action{type, command, format, exponent};
121         EXPECT_EQ(action.execute(env), true);
122     }
123     catch (...)
124     {
125         ADD_FAILURE() << "Should not have caught exception.";
126     }
127 
128     // Test where works: linear_16 format: exponent specified in constructor
129     try
130     {
131         // Determine READ_VOUT linear data value and decimal value
132         // * Exponent: -8
133         // * 16 bit mantissa: 816 = 0000 0011 0011 0000
134         // * linear data format = 0000 0011 0011 0000 = 0x0330
135         // * Decimal value: 816 * 2^(-8) = 3.1875
136 
137         // Create mock I2CInterface.  Expect action to do the following:
138         // * will read 0x0330 from READ_VOUT (command/register 0x8B)
139         // * will not read from VOUT_MODE (command/register 0x20)
140         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
141             std::make_unique<i2c::MockedI2CInterface>();
142         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
143         EXPECT_CALL(*i2cInterface, read(TypedEq<uint8_t>(0x8B), A<uint16_t&>()))
144             .Times(1)
145             .WillOnce(SetArgReferee<1>(0x0330));
146         EXPECT_CALL(*i2cInterface, read(A<uint8_t>(), A<uint8_t&>())).Times(0);
147 
148         // Create MockServices.  Expect the sensor value to be set.
149         MockServices services{};
150         MockSensors& sensors = services.getMockSensors();
151         EXPECT_CALL(sensors, setValue(SensorType::vout, 3.1875)).Times(1);
152 
153         // Create Device, IDMap, and ActionEnvironment
154         Device device{
155             "reg1", true,
156             "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg1",
157             std::move(i2cInterface)};
158         IDMap idMap{};
159         idMap.addDevice(device);
160         ActionEnvironment env{idMap, "reg1", services};
161 
162         // Create and execute action
163         SensorType type{SensorType::vout};
164         uint8_t command{0x8B};
165         SensorDataFormat format{SensorDataFormat::linear_16};
166         std::optional<int8_t> exponent{-8};
167         PMBusReadSensorAction action{type, command, format, exponent};
168         EXPECT_EQ(action.execute(env), true);
169     }
170     catch (...)
171     {
172         ADD_FAILURE() << "Should not have caught exception.";
173     }
174 
175     // Test where works: linear_16 format: exponent not specified in constructor
176     try
177     {
178         // Determine READ_VOUT linear data value and decimal value
179         // * Exponent: -8
180         // * 16 bit mantissa: 816 = 0000 0011 0011 0000
181         // * linear data format = 0000 0011 0011 0000 = 0x0330
182         // * Decimal value: 816 * 2^(-8) = 3.1875
183 
184         // Create mock I2CInterface.  Expect action to do the following:
185         // * will read 0x0330 from READ_VOUT (command/register 0x8B)
186         // * will read 0b0001'1000 (linear format, -8 exponent) from VOUT_MODE
187         //   (command/register 0x20)
188         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
189             std::make_unique<i2c::MockedI2CInterface>();
190         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
191         EXPECT_CALL(*i2cInterface, read(TypedEq<uint8_t>(0x8B), A<uint16_t&>()))
192             .Times(1)
193             .WillOnce(SetArgReferee<1>(0x0330));
194         EXPECT_CALL(*i2cInterface, read(TypedEq<uint8_t>(0x20), A<uint8_t&>()))
195             .Times(1)
196             .WillOnce(SetArgReferee<1>(0b0001'1000));
197 
198         // Create MockServices.  Expect the sensor value to be set.
199         MockServices services{};
200         MockSensors& sensors = services.getMockSensors();
201         EXPECT_CALL(sensors, setValue(SensorType::vout, 3.1875)).Times(1);
202 
203         // Create Device, IDMap, and ActionEnvironment
204         Device device{
205             "reg1", true,
206             "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg1",
207             std::move(i2cInterface)};
208         IDMap idMap{};
209         idMap.addDevice(device);
210         ActionEnvironment env{idMap, "reg1", services};
211 
212         // Create and execute action
213         SensorType type{SensorType::vout};
214         uint8_t command{0x8B};
215         SensorDataFormat format{SensorDataFormat::linear_16};
216         std::optional<int8_t> exponent{};
217         PMBusReadSensorAction action{type, command, format, exponent};
218         EXPECT_EQ(action.execute(env), true);
219     }
220     catch (...)
221     {
222         ADD_FAILURE() << "Should not have caught exception.";
223     }
224 
225     // Test where fails: Unable to get I2C interface to current device
226     try
227     {
228         // Create IDMap, MockServices, and ActionEnvironment
229         IDMap idMap{};
230         MockServices services{};
231         ActionEnvironment env{idMap, "reg1", services};
232 
233         // Create and execute action
234         SensorType type{SensorType::pout};
235         uint8_t command{0x96};
236         SensorDataFormat format{SensorDataFormat::linear_11};
237         std::optional<int8_t> exponent{};
238         PMBusReadSensorAction action{type, command, format, exponent};
239         action.execute(env);
240         ADD_FAILURE() << "Should not have reached this line.";
241     }
242     catch (const std::invalid_argument& e)
243     {
244         EXPECT_STREQ(e.what(), "Unable to find device with ID \"reg1\"");
245     }
246     catch (...)
247     {
248         ADD_FAILURE() << "Should not have caught exception.";
249     }
250 
251     // Test where fails: VOUT_MODE data format is not linear
252     try
253     {
254         // Create mock I2CInterface.  Expect action to do the following:
255         // * will read READ_VOUT (command/register 0x8B)
256         // * will read 0b0010'0000 (VID data format) from VOUT_MODE
257         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
258             std::make_unique<i2c::MockedI2CInterface>();
259         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
260         EXPECT_CALL(*i2cInterface, read(TypedEq<uint8_t>(0x8B), A<uint16_t&>()))
261             .Times(1);
262         EXPECT_CALL(*i2cInterface, read(TypedEq<uint8_t>(0x20), A<uint8_t&>()))
263             .Times(1)
264             .WillOnce(SetArgReferee<1>(0b0010'0000));
265 
266         // Create Device, IDMap, MockServices, and ActionEnvironment
267         Device device{
268             "reg1", true,
269             "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg1",
270             std::move(i2cInterface)};
271         IDMap idMap{};
272         idMap.addDevice(device);
273         MockServices services{};
274         ActionEnvironment env{idMap, "reg1", services};
275 
276         // Create and execute action
277         SensorType type{SensorType::vout};
278         uint8_t command{0x8B};
279         SensorDataFormat format{SensorDataFormat::linear_16};
280         std::optional<int8_t> exponent{};
281         PMBusReadSensorAction action{type, command, format, exponent};
282         action.execute(env);
283         ADD_FAILURE() << "Should not have reached this line.";
284     }
285     catch (const ActionError& e)
286     {
287         EXPECT_STREQ(e.what(), "ActionError: pmbus_read_sensor: { type: vout, "
288                                "command: 0x8B, format: linear_16 }");
289         try
290         {
291             // Re-throw inner PMBusError
292             std::rethrow_if_nested(e);
293             ADD_FAILURE() << "Should not have reached this line.";
294         }
295         catch (const PMBusError& pe)
296         {
297             EXPECT_STREQ(
298                 pe.what(),
299                 "PMBusError: VOUT_MODE contains unsupported data format");
300             EXPECT_EQ(pe.getDeviceID(), "reg1");
301             EXPECT_EQ(pe.getInventoryPath(), "/xyz/openbmc_project/inventory/"
302                                              "system/chassis/motherboard/reg1");
303         }
304         catch (...)
305         {
306             ADD_FAILURE() << "Should not have caught exception.";
307         }
308     }
309     catch (...)
310     {
311         ADD_FAILURE() << "Should not have caught exception.";
312     }
313 
314     // Test where fails: Reading VOUT_MODE fails
315     try
316     {
317         // Create mock I2CInterface.  Expect action to do the following:
318         // * will read command/register 0xC6
319         // * will try to read VOUT_MODE; exception will be thrown
320         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
321             std::make_unique<i2c::MockedI2CInterface>();
322         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
323         EXPECT_CALL(*i2cInterface, read(TypedEq<uint8_t>(0xC6), A<uint16_t&>()))
324             .Times(1);
325         EXPECT_CALL(*i2cInterface, read(TypedEq<uint8_t>(0x20), A<uint8_t&>()))
326             .Times(1)
327             .WillOnce(Throw(
328                 i2c::I2CException{"Failed to read byte", "/dev/i2c-1", 0x70}));
329 
330         // Create Device, IDMap, MockServices, and ActionEnvironment
331         Device device{
332             "reg1", true,
333             "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg1",
334             std::move(i2cInterface)};
335         IDMap idMap{};
336         idMap.addDevice(device);
337         MockServices services{};
338         ActionEnvironment env{idMap, "reg1", services};
339 
340         // Create and execute action
341         SensorType type{SensorType::vout_peak};
342         uint8_t command{0xC6};
343         SensorDataFormat format{SensorDataFormat::linear_16};
344         std::optional<int8_t> exponent{};
345         PMBusReadSensorAction action{type, command, format, exponent};
346         action.execute(env);
347         ADD_FAILURE() << "Should not have reached this line.";
348     }
349     catch (const ActionError& e)
350     {
351         EXPECT_STREQ(e.what(),
352                      "ActionError: pmbus_read_sensor: { type: vout_peak, "
353                      "command: 0xC6, format: linear_16 }");
354         try
355         {
356             // Re-throw inner I2CException
357             std::rethrow_if_nested(e);
358             ADD_FAILURE() << "Should not have reached this line.";
359         }
360         catch (const i2c::I2CException& ie)
361         {
362             EXPECT_STREQ(
363                 ie.what(),
364                 "I2CException: Failed to read byte: bus /dev/i2c-1, addr 0x70");
365         }
366         catch (...)
367         {
368             ADD_FAILURE() << "Should not have caught exception.";
369         }
370     }
371     catch (...)
372     {
373         ADD_FAILURE() << "Should not have caught exception.";
374     }
375 
376     // Test where fails: Reading PMBus command code with sensor value fails
377     try
378     {
379         // Create mock I2CInterface.  Expect action to do the following:
380         // * will try to read command/register 0x96; exception will be thrown
381         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
382             std::make_unique<i2c::MockedI2CInterface>();
383         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
384         EXPECT_CALL(*i2cInterface, read(TypedEq<uint8_t>(0x96), A<uint16_t&>()))
385             .Times(1)
386             .WillOnce(Throw(i2c::I2CException{"Failed to read word data",
387                                               "/dev/i2c-1", 0x70}));
388 
389         // Create Device, IDMap, MockServices, and ActionEnvironment
390         Device device{
391             "reg1", true,
392             "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg1",
393             std::move(i2cInterface)};
394         IDMap idMap{};
395         idMap.addDevice(device);
396         MockServices services{};
397         ActionEnvironment env{idMap, "reg1", services};
398 
399         // Create and execute action
400         SensorType type{SensorType::pout};
401         uint8_t command{0x96};
402         SensorDataFormat format{SensorDataFormat::linear_11};
403         std::optional<int8_t> exponent{};
404         PMBusReadSensorAction action{type, command, format, exponent};
405         action.execute(env);
406         ADD_FAILURE() << "Should not have reached this line.";
407     }
408     catch (const ActionError& e)
409     {
410         EXPECT_STREQ(e.what(), "ActionError: pmbus_read_sensor: { type: pout, "
411                                "command: 0x96, format: linear_11 }");
412         try
413         {
414             // Re-throw inner I2CException
415             std::rethrow_if_nested(e);
416             ADD_FAILURE() << "Should not have reached this line.";
417         }
418         catch (const i2c::I2CException& ie)
419         {
420             EXPECT_STREQ(ie.what(), "I2CException: Failed to read word data: "
421                                     "bus /dev/i2c-1, addr 0x70");
422         }
423         catch (...)
424         {
425             ADD_FAILURE() << "Should not have caught exception.";
426         }
427     }
428     catch (...)
429     {
430         ADD_FAILURE() << "Should not have caught exception.";
431     }
432 
433     // Test where fails: Unable to publish sensor value due to D-Bus exception
434     try
435     {
436         // Determine READ_IOUT linear data value and decimal value
437         // * 5 bit exponent: -6 = 11010
438         // * 11 bit mantissa: 736 = 010 1110 0000
439         // * linear data format = 1101 0010 1110 0000 = 0xD2E0
440         // * Decimal value: 736 * 2^(-6) = 11.5
441 
442         // Create mock I2CInterface.  Expect action to do the following:
443         // * will read 0xD2E0 from READ_IOUT (command/register 0x8C)
444         // * will not read from VOUT_MODE (command/register 0x20)
445         std::unique_ptr<i2c::MockedI2CInterface> i2cInterface =
446             std::make_unique<i2c::MockedI2CInterface>();
447         EXPECT_CALL(*i2cInterface, isOpen).Times(1).WillOnce(Return(true));
448         EXPECT_CALL(*i2cInterface, read(TypedEq<uint8_t>(0x8C), A<uint16_t&>()))
449             .Times(1)
450             .WillOnce(SetArgReferee<1>(0xD2E0));
451         EXPECT_CALL(*i2cInterface, read(A<uint8_t>(), A<uint8_t&>())).Times(0);
452 
453         // Create MockServices.  Will throw D-Bus exception when trying to set
454         // sensor value.
455         MockServices services{};
456         MockSensors& sensors = services.getMockSensors();
457         EXPECT_CALL(sensors, setValue(SensorType::iout, 11.5))
458             .Times(1)
459             .WillOnce(Throw(TestSDBusError{"D-Bus error: Invalid property"}));
460 
461         // Create Device, IDMap, and ActionEnvironment
462         Device device{
463             "reg1", true,
464             "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg1",
465             std::move(i2cInterface)};
466         IDMap idMap{};
467         idMap.addDevice(device);
468         ActionEnvironment env{idMap, "reg1", services};
469 
470         // Create and execute action
471         SensorType type{SensorType::iout};
472         uint8_t command{0x8C};
473         SensorDataFormat format{SensorDataFormat::linear_11};
474         std::optional<int8_t> exponent{};
475         PMBusReadSensorAction action{type, command, format, exponent};
476         action.execute(env);
477         ADD_FAILURE() << "Should not have reached this line.";
478     }
479     catch (const ActionError& e)
480     {
481         EXPECT_STREQ(e.what(), "ActionError: pmbus_read_sensor: { type: iout, "
482                                "command: 0x8C, format: linear_11 }");
483         try
484         {
485             // Re-throw inner D-Bus exception
486             std::rethrow_if_nested(e);
487             ADD_FAILURE() << "Should not have reached this line.";
488         }
489         catch (const sdbusplus::exception_t& de)
490         {
491             EXPECT_STREQ(de.what(), "D-Bus error: Invalid property");
492         }
493         catch (...)
494         {
495             ADD_FAILURE() << "Should not have caught exception.";
496         }
497     }
498     catch (...)
499     {
500         ADD_FAILURE() << "Should not have caught exception.";
501     }
502 }
503 
TEST(PMBusReadSensorActionTests,GetCommand)504 TEST(PMBusReadSensorActionTests, GetCommand)
505 {
506     SensorType type{SensorType::iout};
507     uint8_t command{0x8C};
508     SensorDataFormat format{SensorDataFormat::linear_11};
509     std::optional<int8_t> exponent{};
510     PMBusReadSensorAction action{type, command, format, exponent};
511     EXPECT_EQ(action.getCommand(), 0x8C);
512 }
513 
TEST(PMBusReadSensorActionTests,GetExponent)514 TEST(PMBusReadSensorActionTests, GetExponent)
515 {
516     SensorType type{SensorType::vout};
517     uint8_t command{0x8B};
518     SensorDataFormat format{SensorDataFormat::linear_16};
519 
520     // Exponent value is specified
521     {
522         std::optional<int8_t> exponent{-9};
523         PMBusReadSensorAction action{type, command, format, exponent};
524         EXPECT_EQ(action.getExponent().has_value(), true);
525         EXPECT_EQ(action.getExponent().value(), -9);
526     }
527 
528     // Exponent value is not specified
529     {
530         std::optional<int8_t> exponent{};
531         PMBusReadSensorAction action{type, command, format, exponent};
532         EXPECT_EQ(action.getExponent().has_value(), false);
533     }
534 }
535 
TEST(PMBusReadSensorActionTests,GetFormat)536 TEST(PMBusReadSensorActionTests, GetFormat)
537 {
538     SensorType type{SensorType::iout};
539     uint8_t command{0x8C};
540     SensorDataFormat format{SensorDataFormat::linear_11};
541     std::optional<int8_t> exponent{};
542     PMBusReadSensorAction action{type, command, format, exponent};
543     EXPECT_EQ(action.getFormat(), SensorDataFormat::linear_11);
544 }
545 
TEST(PMBusReadSensorActionTests,GetType)546 TEST(PMBusReadSensorActionTests, GetType)
547 {
548     SensorType type{SensorType::pout};
549     uint8_t command{0x96};
550     SensorDataFormat format{SensorDataFormat::linear_11};
551     std::optional<int8_t> exponent{};
552     PMBusReadSensorAction action{type, command, format, exponent};
553     EXPECT_EQ(action.getType(), SensorType::pout);
554 }
555 
TEST(PMBusReadSensorActionTests,ToString)556 TEST(PMBusReadSensorActionTests, ToString)
557 {
558     // Test where exponent value is specified
559     {
560         SensorType type{SensorType::vout_peak};
561         uint8_t command{0xC6};
562         SensorDataFormat format{SensorDataFormat::linear_16};
563         std::optional<int8_t> exponent{-8};
564         PMBusReadSensorAction action{type, command, format, exponent};
565         EXPECT_EQ(action.toString(), "pmbus_read_sensor: { type: "
566                                      "vout_peak, command: 0xC6, format: "
567                                      "linear_16, exponent: -8 }");
568     }
569 
570     // Test where exponent value is not specified
571     {
572         SensorType type{SensorType::iout_valley};
573         uint8_t command{0xCB};
574         SensorDataFormat format{SensorDataFormat::linear_11};
575         std::optional<int8_t> exponent{};
576         PMBusReadSensorAction action{type, command, format, exponent};
577         EXPECT_EQ(action.toString(), "pmbus_read_sensor: { type: iout_valley, "
578                                      "command: 0xCB, format: linear_11 }");
579     }
580 }
581