blob: 5e3d6b4b76735dca63cbc8f5faccf51433710c1f [file] [log] [blame]
Brandon Wyman3f1242f2020-01-28 13:11:25 -06001#include "../power_supply.hpp"
2#include "mock.hpp"
3
4#include <xyz/openbmc_project/Common/Device/error.hpp>
5#include <xyz/openbmc_project/Common/error.hpp>
6
7#include <gmock/gmock.h>
8#include <gtest/gtest.h>
9
10using namespace phosphor::power::psu;
11using namespace phosphor::pmbus;
12
13using ::testing::_;
Brandon Wyman59a35792020-06-04 12:37:40 -050014using ::testing::Args;
Brandon Wyman3f1242f2020-01-28 13:11:25 -060015using ::testing::Assign;
16using ::testing::DoAll;
Brandon Wyman59a35792020-06-04 12:37:40 -050017using ::testing::ElementsAre;
18using ::testing::NotNull;
Brandon Wyman3f1242f2020-01-28 13:11:25 -060019using ::testing::Return;
20using ::testing::StrEq;
21
22static auto PSUInventoryPath = "/xyz/bmc/inv/sys/chassis/board/powersupply0";
B. J. Wyman681b2a32021-04-20 22:31:22 +000023static auto PSUGPIOLineName = "presence-ps0";
Brandon Wyman3f1242f2020-01-28 13:11:25 -060024
Brandon Wyman8da35c52021-10-28 22:45:08 +000025// Helper function to setup expectations for various STATUS_* commands
26void setPMBusExpectations(MockedPMBus& mockPMBus, uint16_t statusWordValue,
27 uint8_t statusInputValue = 0,
28 uint8_t statusMFRValue = 0,
Brandon Wyman6710ba22021-10-27 17:39:31 +000029 uint8_t statusCMLValue = 0,
30 uint8_t statusVOUTValue = 0)
Brandon Wyman8da35c52021-10-28 22:45:08 +000031{
32 EXPECT_CALL(mockPMBus, read(STATUS_WORD, _))
33 .Times(1)
34 .WillOnce(Return(statusWordValue));
35
36 if (statusWordValue != 0)
37 {
38 // If fault bits are on in STATUS_WORD, there will also be a read of
39 // STATUS_INPUT, STATUS_MFR, and STATUS_CML.
40 EXPECT_CALL(mockPMBus, read(STATUS_INPUT, _))
41 .Times(1)
42 .WillOnce(Return(statusInputValue));
43 EXPECT_CALL(mockPMBus, read(STATUS_MFR, _))
44 .Times(1)
45 .WillOnce(Return(statusMFRValue));
46 EXPECT_CALL(mockPMBus, read(STATUS_CML, _))
47 .Times(1)
48 .WillOnce(Return(statusCMLValue));
Brandon Wyman6710ba22021-10-27 17:39:31 +000049 // Page will need to be set to 0 to read STATUS_VOUT.
50 EXPECT_CALL(mockPMBus, insertPageNum(STATUS_VOUT, 0))
51 .Times(1)
52 .WillOnce(Return("status0_vout"));
53 EXPECT_CALL(mockPMBus, read("status0_vout", _))
54 .Times(1)
55 .WillOnce(Return(statusVOUTValue));
Brandon Wyman8da35c52021-10-28 22:45:08 +000056 }
57}
58
Brandon Wyman3f1242f2020-01-28 13:11:25 -060059class PowerSupplyTests : public ::testing::Test
60{
61 public:
62 PowerSupplyTests() :
63 mockedUtil(reinterpret_cast<const MockedUtil&>(getUtils()))
64 {
65 ON_CALL(mockedUtil, getPresence(_, _)).WillByDefault(Return(false));
66 }
67
68 ~PowerSupplyTests() override
69 {
70 freeUtils();
71 }
72
73 const MockedUtil& mockedUtil;
74};
75
76TEST_F(PowerSupplyTests, Constructor)
77{
78 /**
79 * @param[in] invpath - String for inventory path to use
80 * @param[in] i2cbus - The bus number this power supply is on
81 * @param[in] i2caddr - The 16-bit I2C address of the power supply
B. J. Wyman681b2a32021-04-20 22:31:22 +000082 * @param[in] gpioLineName - The string for the gpio-line-name to read for
83 * presence.
84 * @param[in] bindDelay - Time in milliseconds to delay binding the device
85 * driver after seeing the presence line go active.
Brandon Wyman3f1242f2020-01-28 13:11:25 -060086 */
87 auto bus = sdbusplus::bus::new_default();
Brandon Wyman3f1242f2020-01-28 13:11:25 -060088
Brandon Wyman1d7a7df2020-03-26 10:14:05 -050089 // Try where inventory path is empty, constructor should fail.
90 try
91 {
B. J. Wyman681b2a32021-04-20 22:31:22 +000092 auto psu =
93 std::make_unique<PowerSupply>(bus, "", 3, 0x68, PSUGPIOLineName);
Brandon Wyman1d7a7df2020-03-26 10:14:05 -050094 ADD_FAILURE() << "Should not have reached this line.";
95 }
96 catch (const std::invalid_argument& e)
97 {
98 EXPECT_STREQ(e.what(), "Invalid empty inventoryPath");
99 }
100 catch (...)
101 {
102 ADD_FAILURE() << "Should not have caught exception.";
103 }
104
B. J. Wyman681b2a32021-04-20 22:31:22 +0000105 // TODO: Try invalid i2c address?
106
107 // Try where gpioLineName is empty.
Brandon Wyman1d7a7df2020-03-26 10:14:05 -0500108 try
109 {
Brandon Wyman1d7a7df2020-03-26 10:14:05 -0500110 auto psu =
B. J. Wyman681b2a32021-04-20 22:31:22 +0000111 std::make_unique<PowerSupply>(bus, PSUInventoryPath, 3, 0x68, "");
112 ADD_FAILURE()
113 << "Should not have reached this line. Invalid gpioLineName.";
114 }
115 catch (const std::invalid_argument& e)
116 {
117 EXPECT_STREQ(e.what(), "Invalid empty gpioLineName");
118 }
119 catch (...)
120 {
121 ADD_FAILURE() << "Should not have caught exception.";
122 }
123
124 // Test with valid arguments
125 // NOT using D-Bus inventory path for presence.
126 try
127 {
128 auto psu = std::make_unique<PowerSupply>(bus, PSUInventoryPath, 3, 0x68,
129 PSUGPIOLineName);
Brandon Wyman1d7a7df2020-03-26 10:14:05 -0500130
131 EXPECT_EQ(psu->isPresent(), false);
132 EXPECT_EQ(psu->isFaulted(), false);
Brandon Wyman8da35c52021-10-28 22:45:08 +0000133 EXPECT_EQ(psu->hasCommFault(), false);
Brandon Wyman1d7a7df2020-03-26 10:14:05 -0500134 EXPECT_EQ(psu->hasInputFault(), false);
135 EXPECT_EQ(psu->hasMFRFault(), false);
136 EXPECT_EQ(psu->hasVINUVFault(), false);
Brandon Wyman6710ba22021-10-27 17:39:31 +0000137 EXPECT_EQ(psu->hasVoutOVFault(), false);
Brandon Wyman1d7a7df2020-03-26 10:14:05 -0500138 }
139 catch (...)
140 {
141 ADD_FAILURE() << "Should not have caught exception.";
142 }
B. J. Wyman681b2a32021-04-20 22:31:22 +0000143
144 // Test with valid arguments
145 // TODO: Using D-Bus inventory path for presence.
146 try
147 {
148 // FIXME: How do I get that presenceGPIO.read() in the startup to throw
149 // an exception?
150
151 // EXPECT_CALL(mockedUtil, getPresence(_,
152 // StrEq(PSUInventoryPath)))
153 // .Times(1);
154 }
155 catch (...)
156 {
157 ADD_FAILURE() << "Should not have caught exception.";
158 }
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600159}
160
161TEST_F(PowerSupplyTests, Analyze)
162{
163 auto bus = sdbusplus::bus::new_default();
164
B. J. Wyman681b2a32021-04-20 22:31:22 +0000165 // If I default to reading the GPIO, I will NOT expect a call to
166 // getPresence().
167
168 PowerSupply psu{bus, PSUInventoryPath, 4, 0x69, PSUGPIOLineName};
Adriana Kobylak3ca062a2021-10-20 15:27:23 +0000169 MockedGPIOInterface* mockPresenceGPIO =
170 static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000171 EXPECT_CALL(*mockPresenceGPIO, read()).Times(1).WillOnce(Return(0));
172
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600173 psu.analyze();
174 // By default, nothing should change.
175 EXPECT_EQ(psu.isPresent(), false);
176 EXPECT_EQ(psu.isFaulted(), false);
177 EXPECT_EQ(psu.hasInputFault(), false);
178 EXPECT_EQ(psu.hasMFRFault(), false);
179 EXPECT_EQ(psu.hasVINUVFault(), false);
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000180 EXPECT_EQ(psu.hasCommFault(), false);
Brandon Wyman6710ba22021-10-27 17:39:31 +0000181 EXPECT_EQ(psu.hasVoutOVFault(), false);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600182
B. J. Wyman681b2a32021-04-20 22:31:22 +0000183 PowerSupply psu2{bus, PSUInventoryPath, 5, 0x6a, PSUGPIOLineName};
184 // In order to get the various faults tested, the power supply needs to
185 // be present in order to read from the PMBus device(s).
Adriana Kobylak3ca062a2021-10-20 15:27:23 +0000186 MockedGPIOInterface* mockPresenceGPIO2 =
187 static_cast<MockedGPIOInterface*>(psu2.getPresenceGPIO());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000188 ON_CALL(*mockPresenceGPIO2, read()).WillByDefault(Return(1));
189
190 EXPECT_EQ(psu2.isPresent(), false);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600191
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600192 MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu2.getPMBus());
Brandon Wyman8da35c52021-10-28 22:45:08 +0000193 // Presence change from missing to present will trigger write to
194 // ON_OFF_CONFIG.
195 EXPECT_CALL(mockPMBus, writeBinary(ON_OFF_CONFIG, _, _));
Brandon Wymanf07bc792021-10-12 19:00:35 +0000196 // Presence change from missing to present will trigger in1_input read in
197 // an attempt to get CLEAR_FAULTS called.
198 EXPECT_CALL(mockPMBus, read(READ_VIN, _)).Times(1).WillOnce(Return(206000));
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000199 // STATUS_WORD 0x0000 is powered on, no faults.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000200 uint16_t statusWordValue = 0;
201 uint8_t statusInputValue = 0;
202 setPMBusExpectations(mockPMBus, statusWordValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600203 psu2.analyze();
204 EXPECT_EQ(psu2.isPresent(), true);
205 EXPECT_EQ(psu2.isFaulted(), false);
206 EXPECT_EQ(psu2.hasInputFault(), false);
207 EXPECT_EQ(psu2.hasMFRFault(), false);
208 EXPECT_EQ(psu2.hasVINUVFault(), false);
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000209 EXPECT_EQ(psu2.hasCommFault(), false);
Brandon Wyman6710ba22021-10-27 17:39:31 +0000210 EXPECT_EQ(psu2.hasVoutOVFault(), false);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600211
212 // STATUS_WORD input fault/warn
Brandon Wyman8da35c52021-10-28 22:45:08 +0000213 statusWordValue = (status_word::INPUT_FAULT_WARN);
Brandon Wymanf07bc792021-10-12 19:00:35 +0000214 // STATUS_INPUT fault bits ... on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000215 statusInputValue = 0x38;
Brandon Wyman6710ba22021-10-27 17:39:31 +0000216 // STATUS_MFR, STATUS_CML, and STATUS_VOUT don't care
Brandon Wyman8da35c52021-10-28 22:45:08 +0000217 setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600218 psu2.analyze();
219 EXPECT_EQ(psu2.isPresent(), true);
220 EXPECT_EQ(psu2.isFaulted(), true);
221 EXPECT_EQ(psu2.hasInputFault(), true);
222 EXPECT_EQ(psu2.hasMFRFault(), false);
223 EXPECT_EQ(psu2.hasVINUVFault(), false);
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000224 EXPECT_EQ(psu2.hasCommFault(), false);
Brandon Wyman6710ba22021-10-27 17:39:31 +0000225 EXPECT_EQ(psu2.hasVoutOVFault(), false);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600226
227 // STATUS_WORD INPUT/UV fault.
228 // First need it to return good status, then the fault
Brandon Wyman8da35c52021-10-28 22:45:08 +0000229 statusWordValue = 0;
230 setPMBusExpectations(mockPMBus, statusWordValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600231 psu2.analyze();
Brandon Wyman8da35c52021-10-28 22:45:08 +0000232 // Now set fault bits in STATUS_WORD
233 statusWordValue =
234 (status_word::INPUT_FAULT_WARN | status_word::VIN_UV_FAULT);
235 // STATUS_INPUT fault bits ... on.
236 statusInputValue = 0x38;
Brandon Wyman6710ba22021-10-27 17:39:31 +0000237 // STATUS_MFR, STATUS_CML, and STATUS_VOUT don't care
Brandon Wyman8da35c52021-10-28 22:45:08 +0000238 setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600239 psu2.analyze();
240 EXPECT_EQ(psu2.isPresent(), true);
241 EXPECT_EQ(psu2.isFaulted(), true);
Brandon Wymanf07bc792021-10-12 19:00:35 +0000242 EXPECT_EQ(psu2.hasInputFault(), true);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600243 EXPECT_EQ(psu2.hasMFRFault(), false);
244 EXPECT_EQ(psu2.hasVINUVFault(), true);
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000245 EXPECT_EQ(psu2.hasCommFault(), false);
Brandon Wyman6710ba22021-10-27 17:39:31 +0000246 EXPECT_EQ(psu2.hasVoutOVFault(), false);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600247
248 // STATUS_WORD MFR fault.
Brandon Wymanf07bc792021-10-12 19:00:35 +0000249 // First need it to return good status, then the fault
Brandon Wyman8da35c52021-10-28 22:45:08 +0000250 statusWordValue = 0;
251 setPMBusExpectations(mockPMBus, statusWordValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600252 psu2.analyze();
Brandon Wyman8da35c52021-10-28 22:45:08 +0000253 // Now STATUS_WORD with MFR fault bit on.
254 statusWordValue = (status_word::MFR_SPECIFIC_FAULT);
255 // STATUS_INPUT fault bits ... don't care.
256 statusInputValue = 0;
257 // STATUS_MFR bits on.
258 uint8_t statusMFRValue = 0xFF;
Brandon Wyman6710ba22021-10-27 17:39:31 +0000259 // STATUS_CML and STATUS_VOUT don't care
Brandon Wyman8da35c52021-10-28 22:45:08 +0000260 setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue,
261 statusMFRValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600262 psu2.analyze();
263 EXPECT_EQ(psu2.isPresent(), true);
264 EXPECT_EQ(psu2.isFaulted(), true);
265 EXPECT_EQ(psu2.hasInputFault(), false);
266 EXPECT_EQ(psu2.hasMFRFault(), true);
267 EXPECT_EQ(psu2.hasVINUVFault(), false);
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000268 EXPECT_EQ(psu2.hasCommFault(), false);
Brandon Wyman6710ba22021-10-27 17:39:31 +0000269 EXPECT_EQ(psu2.hasVoutOVFault(), false);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600270
271 // Ignore Temperature fault.
Brandon Wymanf07bc792021-10-12 19:00:35 +0000272 // First STATUS_WORD with no bits set, then with temperature fault.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000273 statusWordValue = 0;
274 setPMBusExpectations(mockPMBus, statusWordValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600275 psu2.analyze();
Brandon Wyman8da35c52021-10-28 22:45:08 +0000276 // STATUS_WORD with temperature fault bit on.
277 statusWordValue = (status_word::TEMPERATURE_FAULT_WARN);
Brandon Wyman6710ba22021-10-27 17:39:31 +0000278 // STATUS_INPUT, STATUS_MFR, STATUS_CML, and STATUS_VOUT fault bits ...
279 // don't care.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000280 setPMBusExpectations(mockPMBus, statusWordValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600281 psu2.analyze();
282 EXPECT_EQ(psu2.isPresent(), true);
283 EXPECT_EQ(psu2.isFaulted(), false);
284 EXPECT_EQ(psu2.hasInputFault(), false);
285 EXPECT_EQ(psu2.hasMFRFault(), false);
286 EXPECT_EQ(psu2.hasVINUVFault(), false);
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000287 EXPECT_EQ(psu2.hasCommFault(), false);
Brandon Wyman6710ba22021-10-27 17:39:31 +0000288 EXPECT_EQ(psu2.hasVoutOVFault(), false);
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000289
290 // CML fault
291 // First STATUS_WORD wit no bits set, then with CML fault.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000292 statusWordValue = 0;
293 setPMBusExpectations(mockPMBus, statusWordValue);
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000294 psu2.analyze();
Brandon Wyman8da35c52021-10-28 22:45:08 +0000295 // STATUS_WORD with CML fault bit on.
296 statusWordValue = (status_word::CML_FAULT);
297 // STATUS_INPUT fault bits ... don't care.
298 statusInputValue = 0;
299 // STATUS_MFR don't care
300 statusMFRValue = 0;
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000301 // Turn on STATUS_CML fault bit(s)
Brandon Wyman8da35c52021-10-28 22:45:08 +0000302 uint8_t statusCMLValue = 0xFF;
Brandon Wyman6710ba22021-10-27 17:39:31 +0000303 // STATUS_VOUT don't care
Brandon Wyman8da35c52021-10-28 22:45:08 +0000304 setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue,
305 statusMFRValue, statusCMLValue);
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000306 psu2.analyze();
307 EXPECT_EQ(psu2.isPresent(), true);
308 EXPECT_EQ(psu2.isFaulted(), true);
309 EXPECT_EQ(psu2.hasInputFault(), false);
310 EXPECT_EQ(psu2.hasMFRFault(), false);
311 EXPECT_EQ(psu2.hasVINUVFault(), false);
312 EXPECT_EQ(psu2.hasCommFault(), true);
Brandon Wyman6710ba22021-10-27 17:39:31 +0000313 EXPECT_EQ(psu2.hasVoutOVFault(), false);
314
315 // VOUT_OV_FAULT fault
316 // First STATUS_WORD with no bits set, then with VOUT/VOUT_OV fault.
317 statusWordValue = 0;
318 setPMBusExpectations(mockPMBus, statusWordValue);
319 psu2.analyze();
320 // STATUS_WORD with VOUT/VOUT_OV fault.
321 statusWordValue =
322 ((status_word::VOUT_FAULT) | (status_word::VOUT_OV_FAULT));
323 // STATUS_INPUT fault bits ... don't care.
324 statusInputValue = 0;
325 // STATUS_MFR don't care
326 statusMFRValue = 0;
327 // STATUS_CML don't care
328 statusCMLValue = 0;
329 // Turn on STATUS_VOUT fault bit(s)
330 uint8_t statusVOUTValue = 0xA0;
331 setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue,
332 statusMFRValue, statusCMLValue, statusVOUTValue);
333 psu2.analyze();
334 EXPECT_EQ(psu2.isPresent(), true);
335 EXPECT_EQ(psu2.isFaulted(), true);
336 EXPECT_EQ(psu2.hasInputFault(), false);
337 EXPECT_EQ(psu2.hasMFRFault(), false);
338 EXPECT_EQ(psu2.hasVINUVFault(), false);
339 EXPECT_EQ(psu2.hasCommFault(), false);
340 EXPECT_EQ(psu2.hasVoutOVFault(), true);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600341
342 // Ignore fan fault
Brandon Wyman8da35c52021-10-28 22:45:08 +0000343 // First STATUS_WORD with no bits set, then with fan
344 // fault.
345 statusWordValue = 0;
346 setPMBusExpectations(mockPMBus, statusWordValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600347 psu2.analyze();
Brandon Wyman8da35c52021-10-28 22:45:08 +0000348 statusWordValue = (status_word::FAN_FAULT);
Brandon Wyman6710ba22021-10-27 17:39:31 +0000349 // STATUS_INPUT, STATUS_MFR, STATUS_CML, STATUS_VOUT: Don't care if
350 // bits set or not.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000351 setPMBusExpectations(mockPMBus, statusWordValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600352 psu2.analyze();
353 EXPECT_EQ(psu2.isPresent(), true);
354 EXPECT_EQ(psu2.isFaulted(), false);
355 EXPECT_EQ(psu2.hasInputFault(), false);
356 EXPECT_EQ(psu2.hasMFRFault(), false);
357 EXPECT_EQ(psu2.hasVINUVFault(), false);
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000358 EXPECT_EQ(psu2.hasCommFault(), false);
Brandon Wyman6710ba22021-10-27 17:39:31 +0000359 EXPECT_EQ(psu2.hasVoutOVFault(), false);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600360
361 // TODO: ReadFailure
362}
363
Brandon Wyman59a35792020-06-04 12:37:40 -0500364TEST_F(PowerSupplyTests, OnOffConfig)
365{
366 auto bus = sdbusplus::bus::new_default();
367 uint8_t data = 0x15;
368
369 // Test where PSU is NOT present
370 try
371 {
B. J. Wyman681b2a32021-04-20 22:31:22 +0000372 // Assume GPIO presence, not inventory presence?
373 PowerSupply psu{bus, PSUInventoryPath, 4, 0x69, PSUGPIOLineName};
374
Adriana Kobylak3ca062a2021-10-20 15:27:23 +0000375 MockedGPIOInterface* mockPresenceGPIO =
376 static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000377 ON_CALL(*mockPresenceGPIO, read()).WillByDefault(Return(0));
Brandon Wyman59a35792020-06-04 12:37:40 -0500378 MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000379 // Constructor should set initial presence, default read returns 0.
Brandon Wyman59a35792020-06-04 12:37:40 -0500380 // If it is not present, I should not be trying to write to it.
381 EXPECT_CALL(mockPMBus, writeBinary(_, _, _)).Times(0);
382 psu.onOffConfig(data);
383 }
384 catch (...)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000385 {}
Brandon Wyman59a35792020-06-04 12:37:40 -0500386
387 // Test where PSU is present
388 try
389 {
B. J. Wyman681b2a32021-04-20 22:31:22 +0000390 // Assume GPIO presence, not inventory presence?
391 PowerSupply psu{bus, PSUInventoryPath, 5, 0x6a, PSUGPIOLineName};
Adriana Kobylak3ca062a2021-10-20 15:27:23 +0000392 MockedGPIOInterface* mockPresenceGPIO =
393 static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000394 ON_CALL(*mockPresenceGPIO, read()).WillByDefault(Return(1));
Brandon Wyman59a35792020-06-04 12:37:40 -0500395 MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000396 // TODO: expect setPresence call?
397 // updatePresence() private function reads gpio, called by analyze().
398 psu.analyze();
Brandon Wyman59a35792020-06-04 12:37:40 -0500399 // TODO: ???should I check the filename?
400 EXPECT_CALL(mockPMBus,
401 writeBinary(_, ElementsAre(0x15), Type::HwmonDeviceDebug))
402 .Times(1);
403 psu.onOffConfig(data);
404 }
405 catch (...)
Adriana Kobylak0c9a33d2021-09-13 18:05:09 +0000406 {}
Brandon Wyman59a35792020-06-04 12:37:40 -0500407}
408
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600409TEST_F(PowerSupplyTests, ClearFaults)
410{
411 auto bus = sdbusplus::bus::new_default();
B. J. Wyman681b2a32021-04-20 22:31:22 +0000412 PowerSupply psu{bus, PSUInventoryPath, 13, 0x68, PSUGPIOLineName};
Adriana Kobylak3ca062a2021-10-20 15:27:23 +0000413 MockedGPIOInterface* mockPresenceGPIO =
414 static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000415 // GPIO read return 1 to indicate present.
416 ON_CALL(*mockPresenceGPIO, read()).WillByDefault(Return(1));
417 MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
Brandon Wyman8da35c52021-10-28 22:45:08 +0000418 // Presence change from missing to present will trigger in1_input read in
419 // an attempt to get CLEAR_FAULTS called.
420 EXPECT_CALL(mockPMBus, read(READ_VIN, _)).Times(1).WillOnce(Return(206000));
421 // STATUS_WORD 0x0000 is powered on, no faults.
422 uint16_t statusWordValue = 0;
423 setPMBusExpectations(mockPMBus, statusWordValue);
B. J. Wyman681b2a32021-04-20 22:31:22 +0000424 psu.analyze();
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600425 EXPECT_EQ(psu.isPresent(), true);
426 EXPECT_EQ(psu.isFaulted(), false);
427 EXPECT_EQ(psu.hasInputFault(), false);
428 EXPECT_EQ(psu.hasMFRFault(), false);
429 EXPECT_EQ(psu.hasVINUVFault(), false);
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000430 EXPECT_EQ(psu.hasCommFault(), false);
Brandon Wyman6710ba22021-10-27 17:39:31 +0000431 EXPECT_EQ(psu.hasVoutOVFault(), false);
Brandon Wymanf07bc792021-10-12 19:00:35 +0000432 // STATUS_WORD with fault bits galore!
Brandon Wyman8da35c52021-10-28 22:45:08 +0000433 statusWordValue = 0xFFFF;
Brandon Wymanf07bc792021-10-12 19:00:35 +0000434 // STATUS_INPUT with fault bits on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000435 uint8_t statusInputValue = 0xFF;
Brandon Wymanf07bc792021-10-12 19:00:35 +0000436 // STATUS_MFR_SPEFIC with bits on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000437 uint8_t statusMFRValue = 0xFF;
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000438 // STATUS_CML with bits on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000439 uint8_t statusCMLValue = 0xFF;
Brandon Wyman6710ba22021-10-27 17:39:31 +0000440 // STATUS_VOUT with bits on.
441 uint8_t statusVOUTValue = 0xFF;
Brandon Wyman8da35c52021-10-28 22:45:08 +0000442 setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue,
Brandon Wyman6710ba22021-10-27 17:39:31 +0000443 statusMFRValue, statusCMLValue, statusVOUTValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600444 psu.analyze();
445 EXPECT_EQ(psu.isPresent(), true);
446 EXPECT_EQ(psu.isFaulted(), true);
447 EXPECT_EQ(psu.hasInputFault(), true);
448 EXPECT_EQ(psu.hasMFRFault(), true);
449 EXPECT_EQ(psu.hasVINUVFault(), true);
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000450 EXPECT_EQ(psu.hasCommFault(), true);
Brandon Wyman6710ba22021-10-27 17:39:31 +0000451 EXPECT_EQ(psu.hasVoutOVFault(), true);
Brandon Wyman3c208462020-05-13 16:25:58 -0500452 EXPECT_CALL(mockPMBus, read("in1_input", _))
453 .Times(1)
454 .WillOnce(Return(209000));
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600455 psu.clearFaults();
456 EXPECT_EQ(psu.isPresent(), true);
457 EXPECT_EQ(psu.isFaulted(), false);
458 EXPECT_EQ(psu.hasInputFault(), false);
459 EXPECT_EQ(psu.hasMFRFault(), false);
460 EXPECT_EQ(psu.hasVINUVFault(), false);
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000461 EXPECT_EQ(psu.hasCommFault(), false);
Brandon Wyman6710ba22021-10-27 17:39:31 +0000462 EXPECT_EQ(psu.hasVoutOVFault(), false);
B. J. Wyman681b2a32021-04-20 22:31:22 +0000463
464 // TODO: Faults clear on missing/present?
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600465}
466
467TEST_F(PowerSupplyTests, UpdateInventory)
468{
469 auto bus = sdbusplus::bus::new_default();
Brandon Wyman1d7a7df2020-03-26 10:14:05 -0500470
471 try
472 {
B. J. Wyman681b2a32021-04-20 22:31:22 +0000473 PowerSupply psu{bus, PSUInventoryPath, 3, 0x68, PSUGPIOLineName};
Brandon Wyman1d7a7df2020-03-26 10:14:05 -0500474 MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
475 // If it is not present, I should not be trying to read a string
476 EXPECT_CALL(mockPMBus, readString(_, _)).Times(0);
477 psu.updateInventory();
478 }
479 catch (...)
480 {
481 ADD_FAILURE() << "Should not have caught exception.";
482 }
483
484 try
485 {
B. J. Wyman681b2a32021-04-20 22:31:22 +0000486 PowerSupply psu{bus, PSUInventoryPath, 13, 0x69, PSUGPIOLineName};
Adriana Kobylak3ca062a2021-10-20 15:27:23 +0000487 MockedGPIOInterface* mockPresenceGPIO =
488 static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000489 // GPIO read return 1 to indicate present.
490 EXPECT_CALL(*mockPresenceGPIO, read()).Times(1).WillOnce(Return(1));
491 psu.analyze();
Brandon Wyman1d7a7df2020-03-26 10:14:05 -0500492 MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
493 EXPECT_CALL(mockPMBus, readString(_, _)).WillRepeatedly(Return(""));
494 psu.updateInventory();
495
Brandon Wyman3c530fb2021-04-13 13:13:22 -0500496#if IBM_VPD
Brandon Wyman1d7a7df2020-03-26 10:14:05 -0500497 EXPECT_CALL(mockPMBus, readString(_, _))
498 .WillOnce(Return("CCIN"))
499 .WillOnce(Return("PN3456"))
500 .WillOnce(Return("FN3456"))
501 .WillOnce(Return("HEADER"))
502 .WillOnce(Return("SN3456"))
503 .WillOnce(Return("FW3456"));
Brandon Wyman3c530fb2021-04-13 13:13:22 -0500504#endif
Brandon Wyman1d7a7df2020-03-26 10:14:05 -0500505 psu.updateInventory();
506 // TODO: D-Bus mocking to verify values stored on D-Bus (???)
507 }
508 catch (...)
509 {
510 ADD_FAILURE() << "Should not have caught exception.";
511 }
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600512}
513
514TEST_F(PowerSupplyTests, IsPresent)
515{
516 auto bus = sdbusplus::bus::new_default();
B. J. Wyman681b2a32021-04-20 22:31:22 +0000517
518 PowerSupply psu{bus, PSUInventoryPath, 3, 0x68, PSUGPIOLineName};
Adriana Kobylak3ca062a2021-10-20 15:27:23 +0000519 MockedGPIOInterface* mockPresenceGPIO =
520 static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600521 EXPECT_EQ(psu.isPresent(), false);
522
B. J. Wyman681b2a32021-04-20 22:31:22 +0000523 // Change GPIO read to return 1 to indicate present.
524 EXPECT_CALL(*mockPresenceGPIO, read()).Times(1).WillOnce(Return(1));
525 psu.analyze();
526 EXPECT_EQ(psu.isPresent(), true);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600527}
528
529TEST_F(PowerSupplyTests, IsFaulted)
530{
531 auto bus = sdbusplus::bus::new_default();
B. J. Wyman681b2a32021-04-20 22:31:22 +0000532
533 PowerSupply psu{bus, PSUInventoryPath, 11, 0x6f, PSUGPIOLineName};
Adriana Kobylak3ca062a2021-10-20 15:27:23 +0000534 MockedGPIOInterface* mockPresenceGPIO =
535 static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000536 // Always return 1 to indicate present.
537 EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
538 psu.analyze();
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600539 EXPECT_EQ(psu.isFaulted(), false);
540 MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
Brandon Wymanf07bc792021-10-12 19:00:35 +0000541 // STATUS_WORD with fault bits on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000542 uint16_t statusWordValue = 0xFFFF;
Brandon Wymanf07bc792021-10-12 19:00:35 +0000543 // STATUS_INPUT with fault bits on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000544 uint8_t statusInputValue = 0xFF;
Brandon Wymanf07bc792021-10-12 19:00:35 +0000545 // STATUS_MFR_SPECIFIC with faults bits on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000546 uint8_t statusMFRValue = 0xFF;
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000547 // STATUS_CML with faults bits on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000548 uint8_t statusCMLValue = 0xFF;
Brandon Wyman6710ba22021-10-27 17:39:31 +0000549 // STATUS_VOUT with fault bits on.
550 uint8_t statusVOUTValue = 0xFF;
Brandon Wyman8da35c52021-10-28 22:45:08 +0000551 setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue,
Brandon Wyman6710ba22021-10-27 17:39:31 +0000552 statusMFRValue, statusCMLValue, statusVOUTValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600553 psu.analyze();
554 EXPECT_EQ(psu.isFaulted(), true);
555}
556
557TEST_F(PowerSupplyTests, HasInputFault)
558{
559 auto bus = sdbusplus::bus::new_default();
B. J. Wyman681b2a32021-04-20 22:31:22 +0000560
561 PowerSupply psu{bus, PSUInventoryPath, 3, 0x68, PSUGPIOLineName};
Adriana Kobylak3ca062a2021-10-20 15:27:23 +0000562 MockedGPIOInterface* mockPresenceGPIO =
563 static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000564 // Always return 1 to indicate present.
565 EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
566 psu.analyze();
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600567 MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
568 EXPECT_EQ(psu.hasInputFault(), false);
Brandon Wyman8da35c52021-10-28 22:45:08 +0000569 // STATUS_WORD 0x0000 is powered on, no faults.
570 uint16_t statusWordValue = 0;
571 setPMBusExpectations(mockPMBus, statusWordValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600572 psu.analyze();
573 EXPECT_EQ(psu.hasInputFault(), false);
Brandon Wymanf07bc792021-10-12 19:00:35 +0000574 // STATUS_WORD with input fault/warn on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000575 statusWordValue = (status_word::INPUT_FAULT_WARN);
Brandon Wymanf07bc792021-10-12 19:00:35 +0000576 // STATUS_INPUT with an input fault bit on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000577 uint8_t statusInputValue = 0x80;
Brandon Wyman6710ba22021-10-27 17:39:31 +0000578 // STATUS_MFR, STATUS_CML, and STATUS_VOUT don't care.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000579 setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600580 psu.analyze();
581 EXPECT_EQ(psu.hasInputFault(), true);
Brandon Wymanf07bc792021-10-12 19:00:35 +0000582 // STATUS_WORD with no bits on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000583 statusWordValue = 0;
584 setPMBusExpectations(mockPMBus, statusWordValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600585 psu.analyze();
586 EXPECT_EQ(psu.hasInputFault(), false);
587}
588
589TEST_F(PowerSupplyTests, HasMFRFault)
590{
591 auto bus = sdbusplus::bus::new_default();
B. J. Wyman681b2a32021-04-20 22:31:22 +0000592
593 PowerSupply psu{bus, PSUInventoryPath, 3, 0x68, PSUGPIOLineName};
Adriana Kobylak3ca062a2021-10-20 15:27:23 +0000594 MockedGPIOInterface* mockPresenceGPIO =
595 static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000596 // Always return 1 to indicate present.
597 EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
598 psu.analyze();
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600599 MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
600 EXPECT_EQ(psu.hasMFRFault(), false);
Brandon Wymanf07bc792021-10-12 19:00:35 +0000601 // First return STATUS_WORD with no bits on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000602 // STATUS_WORD 0x0000 is powered on, no faults.
603 uint16_t statusWordValue = 0;
604 setPMBusExpectations(mockPMBus, statusWordValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600605 psu.analyze();
606 EXPECT_EQ(psu.hasMFRFault(), false);
Brandon Wymanf07bc792021-10-12 19:00:35 +0000607 // Next return STATUS_WORD with MFR fault bit on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000608 statusWordValue = (status_word::MFR_SPECIFIC_FAULT);
Brandon Wymanf07bc792021-10-12 19:00:35 +0000609 // STATUS_INPUT don't care
Brandon Wyman8da35c52021-10-28 22:45:08 +0000610 uint8_t statusInputValue = 0;
Brandon Wymanf07bc792021-10-12 19:00:35 +0000611 // STATUS_MFR_SPEFIC with bit(s) on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000612 uint8_t statusMFRValue = 0xFF;
Brandon Wyman6710ba22021-10-27 17:39:31 +0000613 // STATUS_CML and STATUS_VOUT don't care.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000614 setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue,
615 statusMFRValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600616 psu.analyze();
617 EXPECT_EQ(psu.hasMFRFault(), true);
Brandon Wymanf07bc792021-10-12 19:00:35 +0000618 // Back to no bits on in STATUS_WORD
Brandon Wyman8da35c52021-10-28 22:45:08 +0000619 statusWordValue = 0;
620 setPMBusExpectations(mockPMBus, statusWordValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600621 psu.analyze();
622 EXPECT_EQ(psu.hasMFRFault(), false);
623}
624
625TEST_F(PowerSupplyTests, HasVINUVFault)
626{
627 auto bus = sdbusplus::bus::new_default();
B. J. Wyman681b2a32021-04-20 22:31:22 +0000628
629 PowerSupply psu{bus, PSUInventoryPath, 3, 0x68, PSUGPIOLineName};
Adriana Kobylak3ca062a2021-10-20 15:27:23 +0000630 MockedGPIOInterface* mockPresenceGPIO =
631 static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
B. J. Wyman681b2a32021-04-20 22:31:22 +0000632 // Always return 1 to indicate present.
633 EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
634 psu.analyze();
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600635 MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
636 EXPECT_EQ(psu.hasVINUVFault(), false);
Brandon Wyman8da35c52021-10-28 22:45:08 +0000637 // STATUS_WORD 0x0000 is powered on, no faults.
638 uint16_t statusWordValue = 0;
639 setPMBusExpectations(mockPMBus, statusWordValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600640 psu.analyze();
641 EXPECT_EQ(psu.hasVINUVFault(), false);
Brandon Wymanf07bc792021-10-12 19:00:35 +0000642 // Turn fault on.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000643 statusWordValue = (status_word::VIN_UV_FAULT);
Brandon Wyman85c7bf42021-10-19 22:28:48 +0000644 // Curious disagreement between PMBus Spec. Part II Figure 16 and 33. Go by
645 // Figure 16, and assume bits on in STATUS_INPUT.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000646 uint8_t statusInputValue = 0x18;
Brandon Wyman6710ba22021-10-27 17:39:31 +0000647 // STATUS_MFR, STATUS_CML, and STATUS_VOUT don't care.
Brandon Wyman8da35c52021-10-28 22:45:08 +0000648 setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600649 psu.analyze();
650 EXPECT_EQ(psu.hasVINUVFault(), true);
Brandon Wymanf07bc792021-10-12 19:00:35 +0000651 // Back to no fault bits on in STATUS_WORD
Brandon Wyman8da35c52021-10-28 22:45:08 +0000652 statusWordValue = 0;
653 setPMBusExpectations(mockPMBus, statusWordValue);
Brandon Wyman3f1242f2020-01-28 13:11:25 -0600654 psu.analyze();
655 EXPECT_EQ(psu.hasVINUVFault(), false);
656}
Brandon Wyman6710ba22021-10-27 17:39:31 +0000657
658TEST_F(PowerSupplyTests, HasVoutOVFault)
659{
660 auto bus = sdbusplus::bus::new_default();
661
662 PowerSupply psu{bus, PSUInventoryPath, 3, 0x69, PSUGPIOLineName};
663 MockedGPIOInterface* mockPresenceGPIO =
664 static_cast<MockedGPIOInterface*>(psu.getPresenceGPIO());
665 // Always return 1 to indicate present.
666 EXPECT_CALL(*mockPresenceGPIO, read()).WillRepeatedly(Return(1));
667 psu.analyze();
668 MockedPMBus& mockPMBus = static_cast<MockedPMBus&>(psu.getPMBus());
669 EXPECT_EQ(psu.hasVoutOVFault(), false);
670 // STATUS_WORD 0x0000 is powered on, no faults.
671 uint16_t statusWordValue = 0;
672 setPMBusExpectations(mockPMBus, statusWordValue);
673 psu.analyze();
674 EXPECT_EQ(psu.hasVoutOVFault(), false);
675 // Turn fault on.
676 statusWordValue = (status_word::VOUT_OV_FAULT);
677 // STATUS_INPUT don't care.
678 uint8_t statusInputValue = 0;
679 // STATUS_MFR don't care.
680 uint8_t statusMFRValue = 0;
681 // STATUS_CML don't care.
682 uint8_t statusCMLValue = 0;
683 // STATUS_VOUT fault bit(s)
684 uint8_t statusVOUTValue = 0x80;
685 setPMBusExpectations(mockPMBus, statusWordValue, statusInputValue,
686 statusMFRValue, statusCMLValue, statusVOUTValue);
687 psu.analyze();
688 EXPECT_EQ(psu.hasVoutOVFault(), true);
689 // Back to no fault bits on in STATUS_WORD
690 statusWordValue = 0;
691 setPMBusExpectations(mockPMBus, statusWordValue);
692 psu.analyze();
693 EXPECT_EQ(psu.hasVoutOVFault(), false);
694}