blob: 8627ba280761c1abbdf6f72cc6dd088f022cfdad [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gpio_handler.h"
#include <base/memory/scoped_ptr.h>
#include <base/string_util.h>
#include <base/stringprintf.h>
#include <base/time.h>
#include <glib.h>
#include "update_engine/file_descriptor.h"
using base::Time;
using base::TimeDelta;
using std::string;
using namespace chromeos_update_engine;
namespace chromeos_update_engine {
const char* StandardGpioHandler::gpio_dirs_[kGpioDirMax] = {
"in", // kGpioDirIn
"out", // kGpioDirOut
};
const char* StandardGpioHandler::gpio_vals_[kGpioValMax] = {
"1", // kGpioValUp
"0", // kGpioValDown
};
const StandardGpioHandler::GpioDef
StandardGpioHandler::gpio_defs_[kGpioIdMax] = {
{ "dutflaga", "ID_GPIO_DUTFLAGA" }, // kGpioIdDutflaga
{ "dutflagb", "ID_GPIO_DUTFLAGB" }, // kGpioIdDutflagb
};
unsigned StandardGpioHandler::num_instances_ = 0;
StandardGpioHandler::StandardGpioHandler(UdevInterface* udev_iface,
FileDescriptor* fd,
bool is_defer_discovery,
bool is_cache_test_mode)
: udev_iface_(udev_iface),
fd_(fd),
is_cache_test_mode_(is_cache_test_mode),
is_discovery_attempted_(false),
is_discovery_successful_(false) {
CHECK(udev_iface && fd);
// Ensure there's only one instance of this class.
CHECK_EQ(num_instances_, static_cast<unsigned>(0));
num_instances_++;
// Reset test signal flags.
ResetTestModeSignalingFlags();
// If GPIO discovery not deferred, do it.
if (!is_defer_discovery)
DiscoverGpios();
}
StandardGpioHandler::~StandardGpioHandler() {
num_instances_--;
}
bool StandardGpioHandler::IsTestModeSignaled() {
bool is_returning_cached = false; // for logging purposes
// Attempt GPIO discovery first.
if (DiscoverGpios()) {
// Force a check if so requested.
if (!is_cache_test_mode_)
ResetTestModeSignalingFlags();
is_returning_cached = !is_first_check_; // for logging purposes
if (is_first_check_) {
is_first_check_ = false;
DoTestModeSignalingProtocol();
}
}
LOG(INFO) << "result: " << (is_test_mode_ ? "test" : "normal") << " mode"
<< (is_returning_cached ? " (cached)" : "")
<< (is_handshake_completed_ ? "" : " (default)");
return is_test_mode_;
}
bool StandardGpioHandler::GpioChipUdevEnumHelper::SetupEnumFilters(
udev_enumerate* udev_enum) {
CHECK(udev_enum);
return !(gpio_handler_->udev_iface_->EnumerateAddMatchSubsystem(
udev_enum, "gpio") ||
gpio_handler_->udev_iface_->EnumerateAddMatchSysname(
udev_enum, "gpiochip*"));
}
bool StandardGpioHandler::GpioChipUdevEnumHelper::ProcessDev(udev_device* dev) {
CHECK(dev);
// Ensure we did not encounter more than one chip.
if (num_gpio_chips_++) {
LOG(ERROR) << "enumerated multiple GPIO chips";
return false;
}
// Obtain GPIO descriptors.
for (int id = 0; id < kGpioIdMax; id++) {
const GpioDef* gpio_def = &gpio_defs_[id];
const char* descriptor =
gpio_handler_->udev_iface_->DeviceGetPropertyValue(
dev, gpio_def->udev_property);
if (!descriptor) {
LOG(ERROR) << "could not obtain " << gpio_def->name
<< " descriptor using property " << gpio_def->udev_property;
return false;
}
gpio_handler_->gpios_[id].descriptor = descriptor;
}
return true;
}
bool StandardGpioHandler::GpioChipUdevEnumHelper::Finalize() {
if (num_gpio_chips_ != 1) {
LOG(ERROR) << "could not enumerate a GPIO chip";
return false;
}
return true;
}
bool StandardGpioHandler::GpioUdevEnumHelper::SetupEnumFilters(
udev_enumerate* udev_enum) {
CHECK(udev_enum);
const string gpio_pattern =
string("*").append(gpio_handler_->gpios_[id_].descriptor);
return !(
gpio_handler_->udev_iface_->EnumerateAddMatchSubsystem(
udev_enum, "gpio") ||
gpio_handler_->udev_iface_->EnumerateAddMatchSysname(
udev_enum, gpio_pattern.c_str()));
}
bool StandardGpioHandler::GpioUdevEnumHelper::ProcessDev(udev_device* dev) {
CHECK(dev);
// Ensure we did not encounter more than one GPIO device.
if (num_gpios_++) {
LOG(ERROR) << "enumerated multiple GPIO devices for a given descriptor";
return false;
}
// Obtain GPIO device sysfs path.
const char* dev_path = gpio_handler_->udev_iface_->DeviceGetSyspath(dev);
if (!dev_path) {
LOG(ERROR) << "failed to obtain device syspath for GPIO "
<< gpio_defs_[id_].name;
return false;
}
gpio_handler_->gpios_[id_].dev_path = dev_path;
LOG(INFO) << "obtained device syspath: " << gpio_defs_[id_].name << " -> "
<< gpio_handler_->gpios_[id_].dev_path;
return true;
}
bool StandardGpioHandler::GpioUdevEnumHelper::Finalize() {
if (num_gpios_ != 1) {
LOG(ERROR) << "could not enumerate GPIO device " << gpio_defs_[id_].name;
return false;
}
return true;
}
StandardGpioHandler::GpioDirResetter::GpioDirResetter(
StandardGpioHandler* handler, GpioId id, GpioDir dir) :
do_reset_(false), handler_(handler), id_(id), dir_(dir) {
CHECK(handler);
CHECK_GE(id, 0);
CHECK_LT(id, kGpioIdMax);
CHECK_GE(dir, 0);
CHECK_LT(dir, kGpioDirMax);
}
StandardGpioHandler::GpioDirResetter::~GpioDirResetter() {
if (do_reset_ && !handler_->SetGpioDirection(id_, dir_)) {
LOG(WARNING) << "failed to reset direction of " << gpio_defs_[id_].name
<< " to " << gpio_dirs_[dir_];
}
}
bool StandardGpioHandler::InitUdevEnum(struct udev* udev,
UdevEnumHelper* enum_helper) {
// Obtain a udev enumerate object.
struct udev_enumerate* udev_enum;
if (!(udev_enum = udev_iface_->EnumerateNew(udev))) {
LOG(ERROR) << "failed to obtain udev enumerate context";
return false;
}
// Assign enumerate object to closer.
scoped_ptr<UdevInterface::UdevEnumerateCloser>
udev_enum_closer(udev_iface_->NewUdevEnumerateCloser(&udev_enum));
// Setup enumeration filters.
if (!enum_helper->SetupEnumFilters(udev_enum)) {
LOG(ERROR) << "failed to setup udev enumerate filters";
return false;
}
// Scan for matching devices.
if (udev_iface_->EnumerateScanDevices(udev_enum)) {
LOG(ERROR) << "udev enumerate scan failed";
return false;
}
// Iterate over matching devices.
struct udev_list_entry* list_entry;
for (list_entry = udev_iface_->EnumerateGetListEntry(udev_enum);
list_entry; list_entry = udev_iface_->ListEntryGetNext(list_entry)) {
// Obtain device name.
const char* dev_path = udev_iface_->ListEntryGetName(list_entry);
if (!dev_path) {
LOG(ERROR) << "enumerated device has a null name string";
return false;
}
// Obtain device object.
struct udev_device* dev = udev_iface_->DeviceNewFromSyspath(udev, dev_path);
if (!dev) {
LOG(ERROR) << "obtained a null device object for enumerated device";
return false;
}
scoped_ptr<UdevInterface::UdevDeviceCloser>
dev_closer(udev_iface_->NewUdevDeviceCloser(&dev));
if (!enum_helper->ProcessDev(dev))
return false;
}
// Make sure postconditions were met.
return enum_helper->Finalize();
}
void StandardGpioHandler::ResetTestModeSignalingFlags() {
is_first_check_ = true;
is_handshake_completed_ = false;
is_test_mode_ = false;
}
bool StandardGpioHandler::DiscoverGpios() {
if (is_discovery_attempted_)
return is_discovery_successful_;
is_discovery_attempted_ = true;
// Obtain libudev instance and attach to a dedicated closer.
struct udev* udev;
if (!(udev = udev_iface_->New())) {
LOG(ERROR) << "failed to obtain libudev instance, aborting GPIO discovery";
return false;
}
scoped_ptr<UdevInterface::UdevCloser>
udev_closer(udev_iface_->NewUdevCloser(&udev));
// Enumerate GPIO chips, scanning for GPIO descriptors.
GpioChipUdevEnumHelper chip_enum_helper(this);
if (!InitUdevEnum(udev, &chip_enum_helper)) {
LOG(ERROR) << "enumeration error, aborting GPIO discovery";
return false;
}
// Obtain device names for all discovered GPIOs, reusing the udev instance.
for (int id = 0; id < kGpioIdMax; id++) {
GpioUdevEnumHelper gpio_enum_helper(this, static_cast<GpioId>(id));
if (!InitUdevEnum(udev, &gpio_enum_helper)) {
LOG(ERROR) << "enumeration error, aborting GPIO discovery";
return false;
}
}
is_discovery_successful_ = true;
return true;
}
bool StandardGpioHandler::GetGpioDevName(StandardGpioHandler::GpioId id,
string* dev_path_p) {
CHECK(id >= 0 && id < kGpioIdMax && dev_path_p);
*dev_path_p = gpios_[id].dev_path;
return true;
}
bool StandardGpioHandler::OpenGpioFd(StandardGpioHandler::GpioId id,
const char* dev_name,
bool is_write) {
CHECK(id >= 0 && id < kGpioIdMax && dev_name);
string file_name = StringPrintf("%s/%s", gpios_[id].dev_path.c_str(),
dev_name);
if (!fd_->Open(file_name.c_str(), (is_write ? O_WRONLY : O_RDONLY))) {
if (fd_->IsSettingErrno()) {
PLOG(ERROR) << "failed to open " << file_name
<< " (" << gpio_defs_[id].name << ") for "
<< (is_write ? "writing" : "reading");
} else {
LOG(ERROR) << "failed to open " << file_name
<< " (" << gpio_defs_[id].name << ") for "
<< (is_write ? "writing" : "reading");
}
return false;
}
return true;
}
bool StandardGpioHandler::SetGpio(StandardGpioHandler::GpioId id,
const char* dev_name, const char* entries[],
const int num_entries, int index) {
CHECK_GE(id, 0);
CHECK_LT(id, kGpioIdMax);
CHECK(dev_name);
CHECK(entries);
CHECK_GT(num_entries, 0);
CHECK_GE(index, 0);
CHECK_LT(index, num_entries);
// Open device for writing.
if (!OpenGpioFd(id, dev_name, true))
return false;
ScopedFileDescriptorCloser dev_fd_closer(fd_);
// Write a string corresponding to the requested output index to the GPIO
// device, appending a newline.
string output_str = entries[index];
output_str += '\n';
ssize_t write_len = fd_->Write(output_str.c_str(), output_str.length());
if (write_len != static_cast<ssize_t>(output_str.length())) {
if (write_len < 0) {
const string err_str = "failed to write to GPIO";
if (fd_->IsSettingErrno()) {
PLOG(ERROR) << err_str;
} else {
LOG(ERROR) << err_str;
}
} else {
LOG(ERROR) << "wrong number of bytes written (" << write_len
<< " instead of " << output_str.length() << ")";
}
return false;
}
// Close the device explicitly, returning the close result.
return fd_->Close();
}
bool StandardGpioHandler::GetGpio(StandardGpioHandler::GpioId id,
const char* dev_name, const char* entries[],
const int num_entries, int* index_p) {
CHECK_GE(id, 0);
CHECK_LT(id, kGpioIdMax);
CHECK(dev_name);
CHECK(entries);
CHECK_GT(num_entries, 0);
CHECK(index_p);
// Open device for reading.
if (!OpenGpioFd(id, dev_name, false))
return false;
ScopedFileDescriptorCloser dev_fd_closer(fd_);
// Read the GPIO device. We attempt to read more than the max number of
// characters expected followed by a newline, to ensure that we've indeed read
// all the data available on the device.
size_t max_entry_len = 0;
for (int i = 0; i < num_entries; i++) {
size_t entry_len = strlen(entries[i]);
if (entry_len > max_entry_len)
max_entry_len = entry_len;
}
max_entry_len++; // account for trailing newline
size_t buf_len = max_entry_len + 1; // room for excess char / null terminator
char buf[buf_len];
memset(buf, 0, buf_len);
ssize_t read_len = fd_->Read(buf, buf_len);
if (read_len < 0 || read_len > static_cast<ssize_t>(max_entry_len)) {
if (read_len < 0) {
const string err_str = "failed to read GPIO";
if (fd_->IsSettingErrno()) {
PLOG(ERROR) << err_str;
} else {
LOG(ERROR) << err_str;
}
} else {
LOG(ERROR) << "read too many bytes (" << read_len << ")";
}
return false;
}
// Remove trailing newline.
read_len--;
if (buf[read_len] != '\n') {
LOG(ERROR) << "read value missing trailing newline";
return false;
}
buf[read_len] = '\0';
// Identify and write GPIO status.
for (int i = 0; i < num_entries; i++)
if (!strcmp(entries[i], buf)) {
*index_p = i;
// Close the device explicitly, returning the close result.
return fd_->Close();
}
// Oops, unidentified reading...
LOG(ERROR) << "read unexpected value from GPIO (`" << buf << "')";
return false;
}
bool StandardGpioHandler::SetGpioDirection(StandardGpioHandler::GpioId id,
StandardGpioHandler::GpioDir dir) {
return SetGpio(id, "direction", gpio_dirs_, kGpioDirMax, dir);
}
bool StandardGpioHandler::GetGpioDirection(
StandardGpioHandler::GpioId id,
StandardGpioHandler::GpioDir* direction_p) {
return GetGpio(id, "direction", gpio_dirs_, kGpioDirMax,
reinterpret_cast<int*>(direction_p));
}
bool StandardGpioHandler::SetGpioValue(StandardGpioHandler::GpioId id,
StandardGpioHandler::GpioVal value,
bool is_check_direction) {
// If so instructed, ensure that the GPIO is indeed in the output direction
// before attempting to write to it.
if (is_check_direction) {
GpioDir dir;
if (!(GetGpioDirection(id, &dir) && dir == kGpioDirOut)) {
LOG(ERROR) << "couldn't verify that GPIO is in the output direction "
"prior to reading from it";
return false;
}
}
return SetGpio(id, "value", gpio_vals_, kGpioValMax, value);
}
bool StandardGpioHandler::GetGpioValue(StandardGpioHandler::GpioId id,
StandardGpioHandler::GpioVal* value_p,
bool is_check_direction) {
// If so instructed, ensure that the GPIO is indeed in the input direction
// before attempting to read from it.
if (is_check_direction) {
GpioDir dir;
if (!(GetGpioDirection(id, &dir) && dir == kGpioDirIn)) {
LOG(ERROR) << "couldn't verify that GPIO is in the input direction "
"prior to reading from it";
return false;
}
}
return GetGpio(id, "value", gpio_vals_, kGpioValMax,
reinterpret_cast<int*>(value_p));
}
bool StandardGpioHandler::DoTestModeSignalingProtocol() {
// The test mode signaling protocol is designed to provide a robust indication
// that a Chrome OS device is physically connected to a servo board in a lab
// setting. It is making very few assumptions about the soundness of the
// hardware, firmware and kernel driver implementation of the GPIO mechanism.
// In general, it is performing a three-way handshake between servo and the
// Chrome OS client, based on changes in the GPIO value readings. The
// client-side implementation does the following:
//
// 1. Check for an initial signal (0) on the input GPIO (dut_flaga).
//
// 2. Flip the signal (1 -> 0) on the output GPIO (dut_flagb).
//
// 3. Check for a flipped signal (1) on the input GPIO.
//
// TODO(garnold) the current implementation is based on sysfs access to GPIOs.
// We will likely change this to using a specialized in-kernel driver
// implementation, which would give us better performance and security
// guarantees.
LOG(INFO) << "attempting GPIO handshake";
const char* dutflaga_name = gpio_defs_[kGpioIdDutflaga].name;
const char* dutflagb_name = gpio_defs_[kGpioIdDutflagb].name;
// Flip GPIO direction, set it to "in".
// TODO(garnold) changing the GPIO direction back and forth is necessary for
// overcoming a firmware/kernel issue which causes the device to be in the
// "out" state whereas the kernel thinks it is in the "in" state. This should
// be abandoned once the firmware and/or kernel driver have been fixed.
// Details here: http://code.google.com/p/chromium-os/issues/detail?id=27680
if (!(SetGpioDirection(kGpioIdDutflaga, kGpioDirOut) &&
SetGpioDirection(kGpioIdDutflaga, kGpioDirIn))) {
LOG(ERROR) << "failed to flip direction of input GPIO " << dutflaga_name;
return false;
}
// Peek input GPIO state.
GpioVal dutflaga_gpio_value;
if (!GetGpioValue(kGpioIdDutflaga, &dutflaga_gpio_value, true)) {
LOG(ERROR) << "failed to read input GPIO " << dutflaga_name;
return false;
}
// If initial handshake signal not received, abort.
if (dutflaga_gpio_value != kGpioValDown) {
LOG(INFO) << "input GPIO " << dutflaga_name
<< " unset, terminating handshake";
is_handshake_completed_ = true;
return true;
}
// Initialize output GPIO to a default state.
// TODO(garnold) a similar workaround for possible driver/firmware glitches,
// we insist on flipping the direction of the GPIO prior to assuming it is in
// the "out" direction.
GpioDirResetter dutflagb_dir_resetter(this, kGpioIdDutflagb, kGpioDirIn);
if (!(SetGpioDirection(kGpioIdDutflagb, kGpioDirIn) &&
dutflagb_dir_resetter.set_do_reset(
SetGpioDirection(kGpioIdDutflagb, kGpioDirOut)) &&
SetGpioValue(kGpioIdDutflagb, kGpioValUp, false))) {
LOG(ERROR) << "failed to initialize output GPIO " << dutflagb_name;
return false;
}
// Wait, giving the receiving end enough time to sense the fall.
g_usleep(kServoOutputResponseWaitInSecs * G_USEC_PER_SEC);
// Flip the output signal.
if (!SetGpioValue(kGpioIdDutflagb, kGpioValDown, false)) {
LOG(ERROR) << "failed to flip output GPIO " << dutflagb_name;
return false;
}
// Look for flipped input GPIO value, up to a preset timeout.
Time expires =
Time::Now() + TimeDelta::FromSeconds(kServoInputResponseTimeoutInSecs);
TimeDelta delay =
TimeDelta::FromMicroseconds(1000000 / kServoInputNumChecksPerSec);
bool is_first_response_check = true;
bool is_error = false;
while (Time::Now() < expires) {
if (is_first_response_check)
is_first_response_check = false;
else
g_usleep(delay.InMicroseconds());
// Read input GPIO.
if (!GetGpioValue(kGpioIdDutflaga, &dutflaga_gpio_value, true)) {
LOG(ERROR) << "failed to read input GPIO " << dutflaga_name;
is_error = true;
break;
}
// If dutflaga is now up (flipped), we got our signal!
if (dutflaga_gpio_value == kGpioValUp) {
is_test_mode_ = true;
break;
}
}
if (!is_error) {
if (is_test_mode_) {
is_handshake_completed_ = true;
LOG(INFO) << "GPIO handshake completed, test mode signaled";
} else {
LOG(INFO) << "timed out waiting for input GPIO " << dutflaga_name
<< " to flip, terminating handshake";
}
}
return is_handshake_completed_;
}
bool NoopGpioHandler::IsTestModeSignaled() {
LOG(INFO) << "GPIOs not engaged, defaulting to "
<< (is_test_mode_ ? "test" : "normal") << " mode";
return is_test_mode_;
}
} // namespace chromeos_update_engine