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