blob: cadec4c8ede3d68ba7bfbe3eff7c51bdfb24f757 [file] [log] [blame]
Gilad Arnold6eccc532012-05-17 15:44:22 -07001// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "update_engine/gpio_mock_file_descriptor.h"
6
7#include <base/stringprintf.h>
8#include <gtest/gtest.h>
9
10#include "update_engine/utils.h"
11
12using base::Time;
13using base::TimeDelta;
14using std::string;
15
16namespace chromeos_update_engine {
17
18namespace {
19// Typesets a time object into a string; omits the date.
20string TimeToString(Time time) {
21 Time::Exploded exploded_time;
22 time.LocalExplode(&exploded_time);
23 return StringPrintf("%d:%02d:%02d.%03d",
24 exploded_time.hour,
25 exploded_time.minute,
26 exploded_time.second,
27 exploded_time.millisecond);
28}
29} // namespace
30
31//
32// GpioMockFileDescriptor
33//
34const char* GpioMockFileDescriptor::gpio_devname_prefixes_[kMockGpioIdMax] = {
35 MOCK_SYSFS_PREFIX "/gpio" MOCK_DUTFLAGA_GPIO_ID,
36 MOCK_SYSFS_PREFIX "/gpio" MOCK_DUTFLAGB_GPIO_ID,
37};
38
39const char* GpioMockFileDescriptor::gpio_val_strings_[kMockGpioValMax] = {
40 "1", // kMockGpioValUp
41 "0", // kMockGpioValDown
42};
43
44const char* GpioMockFileDescriptor::gpio_dir_strings_[kMockGpioDirMax] = {
45 "in", // kMockGpioDirIn
46 "out", // kMockGpioDirOut
47};
48
49
50GpioMockFileDescriptor::GpioMockFileDescriptor()
51 : gpio_id_(kMockGpioIdMax),
Gilad Arnold95931b82013-01-09 10:37:17 -080052 gpio_subdev_(kMockGpioSubdevMax),
53 num_open_attempted_(0) {
Gilad Arnold6eccc532012-05-17 15:44:22 -070054 // All GPIOs are initially in the input direction, their read value is "up",
55 // and they assume an initial write value of "up" with current (init) time.
56 Time init_time = Time::Now();
57 for (size_t i = 0; i < kMockGpioIdMax; i++) {
58 gpio_dirs_[i] = kMockGpioDirIn;
59 gpio_read_vals_[i] = kMockGpioValUp;
60 SetGpioLastWrite(static_cast<MockGpioId>(i), kMockGpioValUp, init_time);
61 }
62
63 // Nullify the instance-specific override strings.
64 for (size_t i = 0; i < kMockGpioValMax; i++)
65 override_read_gpio_val_strings_[i] = NULL;
66 for (size_t i = 0; i < kMockGpioDirMax; i++)
67 override_read_gpio_dir_strings_[i] = NULL;
68}
69
70bool GpioMockFileDescriptor::Open(const char* path, int flags, mode_t mode) {
Gilad Arnold95931b82013-01-09 10:37:17 -080071 num_open_attempted_++;
72
Gilad Arnold6eccc532012-05-17 15:44:22 -070073 EXPECT_EQ(gpio_id_, kMockGpioIdMax);
74 if (gpio_id_ != kMockGpioIdMax)
75 return false;
76
77 // Determine identifier of opened GPIO device.
78 size_t devname_prefix_len = 0;
79 int id;
80 for (id = 0; id < kMockGpioIdMax; id++) {
81 devname_prefix_len = strlen(gpio_devname_prefixes_[id]);
82 if (!strncmp(path, gpio_devname_prefixes_[id], devname_prefix_len))
83 break;
84 }
85 EXPECT_LT(id, kMockGpioIdMax);
86 if (id == kMockGpioIdMax)
87 return false;
88
89 // Determine specific sub-device.
90 path += devname_prefix_len;
91 EXPECT_EQ(path[0], '/');
92 if (path[0] != '/')
93 return false;
94 path++;
95 if (!strcmp(path, "value"))
96 gpio_subdev_ = kMockGpioSubdevValue;
97 else if (!strcmp(path, "direction"))
98 gpio_subdev_ = kMockGpioSubdevDirection;
99 else {
100 ADD_FAILURE();
101 return false;
102 }
103
104 gpio_id_ = static_cast<MockGpioId>(id);
105 LOG(INFO) << "opened mock gpio "
106 << (id == kMockGpioIdDutflaga ? "dut_flaga" :
107 id == kMockGpioIdDutflagb ? "dut_flagb" :
108 "<unknown>")
109 << "/"
110 << (gpio_subdev_ == kMockGpioSubdevValue ? "value" :
111 gpio_subdev_ == kMockGpioSubdevDirection ? "direction" :
112 "<unknown>");
113 return true;
114}
115
116bool GpioMockFileDescriptor::Open(const char* path, int flags) {
117 return Open(path, flags, 0);
118}
119
120ssize_t GpioMockFileDescriptor::Read(void* buf, size_t count) {
121 EXPECT_TRUE(IsOpen());
122 if (!IsOpen())
123 return -1;
124
125 LOG(INFO) << "reading from gpio";
126
127 // Attempt a state update prior to responding to the read.
128 UpdateState();
129
130 switch (gpio_subdev_) {
131 case kMockGpioSubdevValue: { // reading the GPIO value
132 // Read values vary depending on the GPIO's direction: an input GPIO will
133 // return the value that was written by the remote end; an output GPIO,
134 // however, will return the value last written to its output register...
135 MockGpioVal gpio_read_val = kMockGpioValMax;
136 switch (gpio_dirs_[gpio_id_]) {
137 case kMockGpioDirIn:
138 gpio_read_val = gpio_read_vals_[gpio_id_];
139 break;
140 case kMockGpioDirOut:
141 gpio_read_val = gpio_last_writes_[gpio_id_].val;
142 break;
143 default:
144 CHECK(false); // shouldn't get here
145 }
146
147 // Write the value to the client's buffer.
148 return snprintf(reinterpret_cast<char*>(buf), count, "%s\n",
149 (override_read_gpio_val_strings_[gpio_read_val] ?
150 override_read_gpio_val_strings_[gpio_read_val] :
151 gpio_val_strings_[gpio_read_val]));
152 }
153
154 case kMockGpioSubdevDirection: { // reading the GPIO direction
155 // Write the current GPIO direction to the client's buffer.
156 MockGpioDir gpio_dir = gpio_dirs_[gpio_id_];
157 return snprintf(reinterpret_cast<char*>(buf), count, "%s\n",
158 (override_read_gpio_dir_strings_[gpio_dir] ?
159 override_read_gpio_dir_strings_[gpio_dir] :
160 gpio_dir_strings_[gpio_dir]));
161 }
162
163 default:
164 ADD_FAILURE(); // shouldn't get here
165 return -1;
166 }
167}
168
169ssize_t GpioMockFileDescriptor::Write(const void* buf, size_t count) {
170 EXPECT_TRUE(IsOpen());
171 EXPECT_TRUE(buf);
172 if (!(IsOpen() && buf))
173 return -1;
174
175 string str = StringPrintf("%-*s", static_cast<int>(count),
176 reinterpret_cast<const char*>(buf));
177 size_t pos = 0;
178 while ((pos = str.find('\n', pos)) != string::npos) {
179 str.replace(pos, 1, "\\n");
180 pos += 2;
181 }
182 LOG(INFO) << "writing to gpio: \"" << str << "\"";
183
184 // Attempt a state update prior to performing the write operation.
185 UpdateState();
186
187 switch (gpio_subdev_) {
188 case kMockGpioSubdevValue: { // setting the GPIO value
189 // Ensure the GPIO is in the "out" direction
190 EXPECT_EQ(gpio_dirs_[gpio_id_], kMockGpioDirOut);
191 if (gpio_dirs_[gpio_id_] != kMockGpioDirOut)
192 return -1;
193
194 // Decode the written value.
195 MockGpioVal write_val = DecodeGpioVal(reinterpret_cast<const char*>(buf),
196 count);
197 EXPECT_LT(write_val, kMockGpioValMax);
198 if (write_val == kMockGpioValMax)
199 return -1;
200
201 // Update the last tracked written value.
202 SetGpioLastWrite(gpio_id_, write_val);
203 break;
204 }
205
206 case kMockGpioSubdevDirection: { // setting GPIO direction
207 // Decipher the direction to be set.
208 MockGpioDir write_dir = DecodeGpioDir(reinterpret_cast<const char*>(buf),
209 count);
210 EXPECT_LT(write_dir, kMockGpioDirMax);
211 if (write_dir == kMockGpioDirMax)
212 return -1;
213
214 // Update the last write time for this GPIO if switching from "in" to
215 // "out" and the written value is different from its read value; this is
216 // due to the GPIO's DUT-side override, which may cause the Servo-side
217 // reading to flip when switching it to "out".
218 if (gpio_dirs_[gpio_id_] == kMockGpioDirIn &&
219 write_dir == kMockGpioDirOut &&
220 gpio_read_vals_[gpio_id_] != gpio_last_writes_[gpio_id_].val)
221 gpio_last_writes_[gpio_id_].time = Time::Now();
222
223 // Now update the GPIO direction.
224 gpio_dirs_[gpio_id_] = write_dir;
225 break;
226 }
227
228 default:
229 ADD_FAILURE(); // shouldn't get here
230 return -1;
231 }
232
233 return count;
234}
235
236bool GpioMockFileDescriptor::Close() {
237 EXPECT_TRUE(IsOpen());
238 if (!IsOpen())
239 return false;
240
241 Reset();
242 return true;
243}
244
245void GpioMockFileDescriptor::Reset() {
246 gpio_id_ = kMockGpioIdMax;
247}
248
249bool GpioMockFileDescriptor::IsSettingErrno() {
250 // This mock doesn't test errno handling, so no.
251 return false;
252}
253
254bool GpioMockFileDescriptor::ExpectAllResourcesDeallocated() {
255 EXPECT_EQ(gpio_id_, kMockGpioIdMax);
256 return (gpio_id_ == kMockGpioIdMax);
257}
258
259bool GpioMockFileDescriptor::ExpectAllGpiosRestoredToDefault() {
260 // We just verify that direction is restored to "in" for all GPIOs.
261 bool is_all_gpios_restored_to_default = true;
262 for (size_t i = 0; i < kMockGpioIdMax; i++) {
263 EXPECT_EQ(gpio_dirs_[i], kMockGpioDirIn)
264 << "i=" << i << " gpio_dirs_[i]=" << gpio_dirs_[i];
265 is_all_gpios_restored_to_default =
266 is_all_gpios_restored_to_default && (gpio_dirs_[i] == kMockGpioDirIn);
267 }
268 return is_all_gpios_restored_to_default;
269}
270
Gilad Arnold95931b82013-01-09 10:37:17 -0800271bool GpioMockFileDescriptor::ExpectNumOpenAttempted(unsigned count) {
272 EXPECT_EQ(num_open_attempted_, count);
273 return (num_open_attempted_ == count);
274}
275
Gilad Arnold6eccc532012-05-17 15:44:22 -0700276size_t GpioMockFileDescriptor::DecodeGpioString(const char* buf,
277 size_t count,
278 const char** strs,
279 size_t num_strs) const {
280 CHECK(buf && strs && count);
281
282 // Last character must be a newline.
283 count--;
284 if (buf[count] != '\n')
285 return num_strs;
286
287 // Scan for a precise match within the provided string array.
288 size_t i;
289 for (i = 0; i < num_strs; i++)
290 if (count == strlen(strs[i]) &&
291 !strncmp(buf, strs[i], count))
292 break;
293 return i;
294}
295
296//
297// TestModeGpioMockFileDescriptor
298//
299TestModeGpioMockFileDescriptor::TestModeGpioMockFileDescriptor(
300 TimeDelta servo_poll_interval)
301 : last_state_(kServoStateInit),
302 servo_poll_interval_(servo_poll_interval) {}
303
304void TestModeGpioMockFileDescriptor::UpdateState() {
305 // The following simulates the Servo state transition logic. Note that all of
306 // these tests are (should be) conservative estimates of the actual,
307 // asynchronous logic implemented by an actual Servo. Also, they should remain
308 // so regardless of which operation (read, write) triggers the check. We
309 // repeat the update cycle until no further state changes occur (which assumes
310 // that there are no state transition cycles).
311 Time curr_time = Time::Now();
312 ServoState curr_state = last_state_;
313 do {
314 if (last_state_ != curr_state) {
315 last_state_ = curr_state;
316 curr_servo_poll_fuzz_ = RandomServoPollFuzz(); // fix a new poll fuzz
317 LOG(INFO) << "state=" << last_state_ << ", new poll fuzz="
318 << utils::FormatTimeDelta(curr_servo_poll_fuzz_);
319 }
320
321 switch (last_state_) {
322 case kServoStateInit:
323 // Unconditionally establish the trigger signal.
324 LOG(INFO) << "unconditionally sending trigger signal over dut_flaga";
325 gpio_read_vals_[kMockGpioIdDutflaga] = kMockGpioValDown;
326 curr_state = kServoStateTriggerSent;
327 break;
328
329 case kServoStateTriggerSent:
330 // If dut_flagb is in "out" mode, its last written value is "1", and
331 // it's probable that Servo has polled it since, then advance the state.
332 if (gpio_dirs_[kMockGpioIdDutflagb] == kMockGpioDirOut &&
333 gpio_last_writes_[kMockGpioIdDutflagb].val == kMockGpioValUp &&
334 (gpio_last_writes_[kMockGpioIdDutflagb].time +
335 curr_servo_poll_fuzz_) < curr_time) {
336 LOG(INFO) << "an up signal was written to dut_flagb on "
337 << TimeToString(gpio_last_writes_[kMockGpioIdDutflagb].time)
338 << " and polled at "
339 << TimeToString(
340 gpio_last_writes_[kMockGpioIdDutflagb].time +
341 curr_servo_poll_fuzz_)
342 << " (after "
343 << utils::FormatTimeDelta(curr_servo_poll_fuzz_)
344 << "); current time is " << TimeToString(curr_time);
345 curr_state = kServoStateChallengeUpReceived;
346 }
347 break;
348
349 case kServoStateChallengeUpReceived:
350 // If dut_flagb is in "out" mode, its last written value is "0", and
351 // it's probable that Servo has polled it since, then advance the state
352 // and flip the value of dut_flaga.
353 if (gpio_dirs_[kMockGpioIdDutflagb] == kMockGpioDirOut &&
354 gpio_last_writes_[kMockGpioIdDutflagb].val == kMockGpioValDown &&
355 (gpio_last_writes_[kMockGpioIdDutflagb].time +
356 curr_servo_poll_fuzz_) < curr_time) {
357 LOG(INFO) << "a down signal was written to dut_flagb on "
358 << TimeToString(gpio_last_writes_[kMockGpioIdDutflagb].time)
359 << " and polled at "
360 << TimeToString(
361 gpio_last_writes_[kMockGpioIdDutflagb].time +
362 curr_servo_poll_fuzz_)
363 << " (after "
364 << utils::FormatTimeDelta(curr_servo_poll_fuzz_)
365 << "); current time is " << TimeToString(curr_time);
366 gpio_read_vals_[kMockGpioIdDutflaga] = kMockGpioValUp;
367 curr_state = kServoStateChallengeDownReceived;
368 }
369 break;
370
371 case kServoStateChallengeDownReceived:
372 break; // terminal state, nothing to do
373
374 default:
375 CHECK(false); // shouldn't get here
376 }
377 } while (last_state_ != curr_state);
378}
379
380//
381// ErrorNormalModeGpioMockFileDescriptor
382//
383ErrorNormalModeGpioMockFileDescriptor::ErrorNormalModeGpioMockFileDescriptor(
384 TimeDelta servo_poll_interval,
385 ErrorNormalModeGpioMockFileDescriptor::GpioError error)
386 : TestModeGpioMockFileDescriptor(servo_poll_interval),
387 error_(error),
388 is_dutflaga_dir_flipped_(false) {}
389
390bool ErrorNormalModeGpioMockFileDescriptor::Open(const char* path, int flags,
391 mode_t mode) {
392 if (error_ == kGpioErrorFailFileOpen)
393 return false;
394 return TestModeGpioMockFileDescriptor::Open(path, flags, mode);
395}
396
397ssize_t ErrorNormalModeGpioMockFileDescriptor::Read(void* buf, size_t count) {
398 if (error_ == kGpioErrorFailFileRead)
399 return -1;
400 return TestModeGpioMockFileDescriptor::Read(buf, count);
401}
402
403ssize_t ErrorNormalModeGpioMockFileDescriptor::Write(const void* buf,
404 size_t count) {
405 if (error_ == kGpioErrorFailFileWrite)
406 return -1;
407 return TestModeGpioMockFileDescriptor::Write(buf, count);
408}
409
410bool ErrorNormalModeGpioMockFileDescriptor::Close() {
411 // We actually need to perform the close operation anyway, to avoid
412 // inconsistencies in the file descriptor's state.
413 bool ret = TestModeGpioMockFileDescriptor::Close();
414 return (error_ == kGpioErrorFailFileClose ? false : ret);
415}
416
417void ErrorNormalModeGpioMockFileDescriptor::UpdateState() {
418 // Invoke the base class's update method.
419 TestModeGpioMockFileDescriptor::UpdateState();
420
421 // Sabotage the normal feedback that is to be expected from the GPIOs, in
422 // various ways based on the requested type of error.
423 switch (error_) {
424 case kGpioErrorFlipInputDir:
425 // Intervene by flipping the direction of dut_flaga right after the
426 // challenge signal was sent to servo. Among other things, this could
427 // simulate a benign race condition, or an intentional attempt to fool the
428 // GPIO module to believe that it is talking to an (absent) servo.
429 if (!is_dutflaga_dir_flipped_ &&
430 last_state_ == kServoStateChallengeDownReceived) {
431 is_dutflaga_dir_flipped_ = true;
432 LOG(INFO) << "intervention: setting dut_flaga direction to out";
433 gpio_dirs_[kMockGpioIdDutflaga] = kMockGpioDirOut;
434 }
435 break;
436
437 case kGpioErrorReadInvalidVal:
438 // Cause the GPIO device to return an invalid value reading.
439 override_read_gpio_val_strings_[kMockGpioValUp] = "foo";
440 override_read_gpio_val_strings_[kMockGpioValDown] = "bar";
441 break;
442
443 case kGpioErrorReadInvalidDir:
444 // Cause the GPIO device to return an invlid direction reading.
445 override_read_gpio_dir_strings_[kMockGpioDirIn] = "boo";
446 override_read_gpio_dir_strings_[kMockGpioDirOut] = "far";
447
448 case kGpioErrorFailFileOpen:
449 case kGpioErrorFailFileRead:
450 case kGpioErrorFailFileWrite:
451 case kGpioErrorFailFileClose:
452 break;
453
454 default:
455 CHECK(false); // shouldn't get here
456 }
457}
458
459bool ErrorNormalModeGpioMockFileDescriptor::ExpectAllGpiosRestoredToDefault() {
460 if (is_dutflaga_dir_flipped_) {
461 LOG(INFO) << "restoring dut_flaga direction back to in";
462 gpio_dirs_[kMockGpioIdDutflaga] = kMockGpioDirIn;
463 }
464
465 return TestModeGpioMockFileDescriptor::ExpectAllGpiosRestoredToDefault();
466}
467
468} // namespace chromeos_update_engine