| // 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 |