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