| // Copyright (c) 2010 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. |
| |
| // NOTE: this file is translated from gpio_setup.py |
| // |
| // A script to create symlinks to platform specific GPIO pins. |
| // |
| // This script creates a set of symlinks pointing at the sys fs files returning |
| // the appropriate GPIO pin values. Each symlink is named to represent the |
| // actual GPIO pin function. |
| // |
| // The location of the symlinks generated by this script can be specified using |
| // the --symlink_root command line option. By default /home/gpio directory is |
| // used. The symlink directory must exist before this script is run. |
| // |
| // The GPIO pins' values are available through a GPIO device present in sys fs. |
| // The device is identified by its PCI bus address. The default PCI address of |
| // the GPIO device (set to 0:0:1f.0), can be changed using the --pci_address |
| // command line option. |
| // |
| // The platform specific bit usage of the GPIO device is derived from the ACPI, |
| // also using files found in a fixed location in sys fs. The default location of |
| // /sys/bus/platform/devices/chromeos_acpi could be changed using the |
| // --acpi_root command line option. |
| // |
| // Each GPIO pin is represented through ACPI as a subdirectory with several |
| // files in it. A typical name of the GPIO pin file looks as follows: |
| // |
| // <acpi_root>/GPIO.<instance>/GPIO.[0-3] |
| // |
| // where <instance> is a zero based number assigned to this GPIO pin by the BIOS |
| // |
| // In particular, file GPIO.0 represents encoded pin signal type (from which the |
| // symlink name is derived), and file GPIO.2 represents the actual zero based |
| // GPIO pin number within this GPIO device range. |
| // |
| // This script reads the ACPI provided mapping, enables the appropriate GPIO |
| // pins and creates symlinks mapping these GPIOs' values. |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <sys/param.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <stdarg.h> |
| #include <ctype.h> |
| #include <getopt.h> |
| #include <glob.h> |
| |
| #include <string> |
| #include <vector> |
| #include <map> |
| |
| #define GPIO_ROOT "/sys/class/gpio" |
| #define GPIO_DEVICE_ROOT GPIO_ROOT "/gpiochip" |
| #define GPIO_ENABLE_FILE GPIO_ROOT "/export" |
| |
| // Can be changed using --pci_address command line option. |
| #define DEFAULT_GPIO_DEVICE_PCI_ADDRESS "0000:00:1f.0" |
| |
| // Can be changed using --acpi_root command line option. |
| #define DEFAULT_ACPI_ROOT "/sys/bus/platform/devices/chromeos_acpi" |
| |
| // can be changed using --symlink_root command line option. |
| #define DEFAULT_SYMLINK_ROOT "/home/gpio" |
| |
| #define GPIO_SIGNAL_TYPE_EXTENSION "0" |
| #define GPIO_ATTRIBUTES_EXTENSION "1" |
| #define GPIO_PIN_NUMBER_EXTENSION "2" |
| |
| // Debug header signal type codes are offset by 0x100, the tuple below |
| // represents the range of valid codes for the debug header. The range |
| // starts at 0x100 and is 0x100 wide. |
| const int GPIO_DEBUG_HEADER_RANGE[2] = { 0x100, 0x100 }; |
| |
| // This dictionary maps GPIO signal types codes into their actual names. |
| const char *GPIO_SIGNAL_TYPES[] = { |
| NULL, // note: 0 is NOT a valid index, so we must |
| // check the range by GPIO_SIGNAL_TYPE_MIN/MAX. |
| "recovery_button", // index: 1 |
| "developer_switch", // index: 2 |
| "write_protect", // index: 3 |
| }; |
| |
| // calculate the dimension and min/max values. |
| #define GPIO_SIGNAL_TYPES_DIM \ |
| (sizeof(GPIO_SIGNAL_TYPES)/sizeof(GPIO_SIGNAL_TYPES[0])) |
| #define GPIO_SIGNAL_TYPE_MIN (1) |
| #define GPIO_SIGNAL_TYPE_MAX (GPIO_SIGNAL_TYPES_DIM-1) |
| // note: the above TYPE_MIN and TYPE_MAX refer to minimal and maximum valid |
| // index values, so the '1' in MAX definition (DIM-1) does not mean |
| // GPIO_SIGNAL_TYPE_MIN; it's for calculation to a 'zero-based array'. |
| |
| /////////////////////////////////////////////////////////////////////// |
| // Utilities for quick python-C translation |
| |
| using std::string; |
| #define GPIO_STRING_BUFFER_LEN (4096) // general buffer length |
| |
| // Works like throwing an exception - directly exit here. |
| static void GpioSetupError(const char *message, ...) { |
| va_list args; |
| va_start(args, message); |
| vfprintf(stderr, message, args); |
| fprintf(stderr, "\n"); |
| va_end(args); |
| exit(1); |
| } |
| |
| // Return: python - open(filename).read().strip() |
| static string open_read_strip(const char *filename) { |
| FILE *fp = fopen(filename, "rt"); |
| string result; |
| if (!fp) |
| return ""; |
| |
| // for virtual files (directly supported by kernel), |
| // it is better to 'read one line of text and strip'. |
| // for all files accessed by this utility should not |
| // contain very long text in first line, so using |
| // GPIO_STRING_BUFFER_LEN should be enough. |
| char buffer[GPIO_STRING_BUFFER_LEN] = ""; |
| |
| if (fgets(buffer, sizeof(buffer), fp) == NULL) { |
| perror(filename); |
| } |
| fclose(fp); |
| |
| // now check leading and trailing spaces |
| char *head = buffer, *tail = head + strlen(head); |
| while (*head && isascii(*head) && isspace(*head)) |
| head++; |
| while (tail > head && isascii(tail[-1]) && isspace(tail[-1])) |
| tail--; |
| |
| return string(head, tail); |
| } |
| |
| static string open_read_strip(const string &filename) { |
| return open_read_strip(filename.c_str()); |
| } |
| |
| static bool os_path_exists(const char *filename) { |
| return ::access(filename, 0) == 0; |
| } |
| |
| static bool os_path_exists(const string &filename) { |
| return os_path_exists(filename.c_str()); |
| } |
| |
| static void os_symlink(const string &src, const string &dest) { |
| if (::symlink(src.c_str(), dest.c_str()) != 0) { |
| perror(src.c_str()); |
| GpioSetupError("cannot create symlink (%s -> %s)", |
| src.c_str(), dest.c_str()); |
| } |
| } |
| |
| static string os_readlink(const string &filename) { |
| char buf[PATH_MAX] = ""; |
| if (::readlink(filename.c_str(), buf, sizeof(buf)-1) == -1) { |
| perror(filename.c_str()); |
| GpioSetupError("cannot read link (%s)", filename.c_str()); |
| } |
| return buf; |
| } |
| |
| static string os_path_abspath(const string &src) { |
| // TODO(hungte) there's no simple equivelent in POSIX. |
| // since this is for debug message only, let's ignore it. |
| return src; |
| } |
| |
| static bool os_path_isdir(const char *filename) { |
| struct stat st = {0}; |
| if (stat(filename, &st) != 0) { |
| perror(filename); |
| GpioSetupError("cannot query path status: %s", filename); |
| } |
| return S_ISDIR(st.st_mode); |
| } |
| |
| static bool os_path_islink(const string &src) { |
| struct stat st = {0}; |
| if (lstat(src.c_str(), &st) != 0) { |
| perror(src.c_str()); |
| GpioSetupError("cannot query link status: %s", src.c_str()); |
| } |
| return S_ISLNK(st.st_mode); |
| } |
| |
| static string os_path_dirname(const string &filename) { |
| // XXX here a full non-directory input (dir+file) name is assumed. |
| assert(!os_path_isdir(filename.c_str())); |
| string newname = filename; |
| size_t pos_slash = newname.rfind('/'); |
| if (pos_slash != newname.npos) |
| newname.erase(pos_slash); |
| return newname; |
| } |
| |
| typedef std::vector<string> GlobResult; |
| |
| static GlobResult glob_glob(const string &pattern) { |
| GlobResult r; |
| glob_t globtok = {0}; |
| if (glob(pattern.c_str(), |
| GLOB_ERR | GLOB_TILDE, NULL, &globtok) == 0) { |
| for (size_t i = 0; i < globtok.gl_pathc; i++) { |
| r.push_back(globtok.gl_pathv[i]); |
| } |
| globfree(&globtok); |
| } |
| return r; |
| } |
| |
| // Return: python - format % (...) |
| static string format_string(const char *format, ...) { |
| char buffer[GPIO_STRING_BUFFER_LEN]; // large enough for current version |
| va_list args; |
| va_start(args, format); |
| vsnprintf(buffer, sizeof(buffer), format, args); |
| va_end(args); |
| return buffer; |
| } |
| |
| /////////////////////////////////////////////////////////////////////// |
| |
| // Represent GPIO chip available through sys fs. |
| // Attributes: |
| // pci_address: a string, PCI address of this GPIO device |
| // base: a number, base global GPIO number of this device (mapped to pin zero |
| // in the device range) |
| // capacity: a number, shows the number of GPIO pins of this device. |
| // description: a multiline string description of this device, initialized |
| // after the device is attached. Can be used to dump device |
| // information. |
| class GpioChip { |
| public: |
| explicit GpioChip(const string &pci_address) { |
| pci_address_ = pci_address; |
| base_ = 0; |
| capacity_ = 0; |
| description_ = "not attached"; |
| } |
| |
| void Attach() { |
| string f; |
| GlobResult r = glob_glob(GPIO_DEVICE_ROOT "*/label"); |
| |
| for (GlobResult::iterator i = r.begin(); i != r.end(); ++i) { |
| string label = open_read_strip(*i); |
| if (label == pci_address_) { |
| f = *i; |
| break; |
| } |
| } |
| |
| if (f.empty()) |
| GpioSetupError("could not find GPIO PCI device %s", pci_address_.c_str()); |
| |
| string directory = os_path_dirname(f); |
| base_ = atoi(open_read_strip(directory + "/base").c_str()); |
| capacity_ = atoi(open_read_strip(directory + "/ngpio").c_str()); |
| description_ = format_string( |
| "GPIO device at PCI address %s\n" |
| "Base gpio pin %d\n" |
| "Capacity %d\n", |
| pci_address_.c_str(), base_, capacity_); |
| } |
| |
| // Enable a certain GPIO pin. |
| // To enable the pin one needs to write its global GPIO number into |
| // /sys/class/gpio/export, if this pin has not been enabled yet. |
| // Inputs: |
| // pin: a number, zero based pin number within this device's range. |
| void EnablePin(int pin) { |
| if (pin >= capacity_) |
| GpioSetupError("pin %d exceeds capacity of %d", pin, capacity_); |
| |
| // XXX in python version, this is named as 'global_gpio_number' |
| // although it's not really global. |
| int gpio_number = base_ + pin; |
| |
| string target = format_string("%s/gpio%d", GPIO_ROOT, gpio_number); |
| if (!os_path_exists(target)) { |
| FILE *fp = fopen(GPIO_ENABLE_FILE, "w"); |
| fprintf(fp, "%d", gpio_number); |
| fclose(fp); |
| } |
| } |
| |
| // quick python-like translators |
| int base() const { return base_; } |
| int capacity() const { return capacity_; } |
| |
| operator const char *() const { |
| return description_.c_str(); |
| } |
| |
| private: |
| string pci_address_; |
| int base_; |
| int capacity_; |
| string description_; |
| |
| GpioChip() { } |
| }; |
| |
| typedef struct { |
| string name; |
| int index; |
| int pin_number; |
| } AcpiMappingEntry; |
| |
| typedef std::vector<AcpiMappingEntry> AcpiMapping; |
| |
| // Scan ACPI information about GPIO and generate a mapping. |
| // |
| // Returns: a list of tuples, each tuple consisting of a string representing |
| // the GPIO pin name and a number, representing the GPIO pin within |
| // the GPIO device space. |
| AcpiMapping ParseAcpiMappings(const string &acpi_root) { |
| GlobResult r = glob_glob(format_string("%s/GPIO.[0-9]*", acpi_root.c_str())); |
| AcpiMapping acpi_gpio_mapping; |
| GlobResult::iterator i; |
| |
| for (i = r.begin(); i != r.end(); i++) { |
| AcpiMappingEntry entry; |
| const char *d = i->c_str(); |
| const char *dot_index_string = strrchr(d, '.'); |
| int signal_type = atoi(open_read_strip( |
| format_string("%s/GPIO.%s", d, GPIO_SIGNAL_TYPE_EXTENSION)).c_str()); |
| |
| assert(dot_index_string); |
| entry.index = atoi(dot_index_string + 1); |
| entry.pin_number = atoi(open_read_strip( |
| format_string("%s/GPIO.%s", d, GPIO_PIN_NUMBER_EXTENSION)).c_str()); |
| |
| if (signal_type >= GPIO_SIGNAL_TYPE_MIN && |
| signal_type <= static_cast<int>(GPIO_SIGNAL_TYPE_MAX) && |
| GPIO_SIGNAL_TYPES[signal_type] != NULL) { |
| entry.name = GPIO_SIGNAL_TYPES[signal_type]; |
| acpi_gpio_mapping.push_back(entry); |
| continue; |
| } |
| |
| // This is not a specific signal, could be a debug header pin. |
| int debug_header = signal_type - GPIO_DEBUG_HEADER_RANGE[0]; |
| if (debug_header >= 0 && debug_header < GPIO_DEBUG_HEADER_RANGE[1]) { |
| entry.name = format_string("debug_header_%d", debug_header); |
| acpi_gpio_mapping.push_back(AcpiMappingEntry(entry)); |
| continue; |
| } |
| |
| // Unrecognized mapping, could happen if BIOS version is ahead of this |
| // script. |
| printf("unknown signal type encoding %d in %s\n", signal_type, d); |
| } |
| |
| if (acpi_gpio_mapping.empty()) |
| GpioSetupError("no gpio mapping found. Is ACPI driver installed?"); |
| |
| return acpi_gpio_mapping; |
| } |
| |
| void CreateSymLink(const string &source_file, const string &symlink) { |
| if (!os_path_exists(symlink)) { |
| os_symlink(source_file.c_str(), symlink.c_str()); |
| return; |
| } |
| |
| if (!os_path_islink(symlink)) |
| GpioSetupError("%s exists but is not a symlink", |
| os_path_abspath(symlink).c_str()); |
| if (os_readlink(symlink) != source_file) |
| GpioSetupError("%s points to a wrong file", |
| os_path_abspath(symlink).c_str()); |
| } |
| |
| void CreateGpioSymlinks(const AcpiMapping& mappings, |
| GpioChip &gpio, |
| const char *acpi_root, |
| const char *symlink_root) { |
| if (!os_path_exists(symlink_root)) |
| GpioSetupError("%s does not exist", symlink_root); |
| |
| if (!os_path_isdir(symlink_root)) |
| GpioSetupError("%s is not a directory", symlink_root); |
| |
| if (access(symlink_root, W_OK) != 0) |
| GpioSetupError("%s is not writable", symlink_root); |
| |
| if (chdir(symlink_root) != 0) |
| GpioSetupError("failed to change directory to %s", symlink_root); |
| |
| AcpiMapping::const_iterator i; |
| for (i = mappings.begin(); i != mappings.end(); ++i) { |
| string symlink, source_file; |
| gpio.EnablePin(i->pin_number); |
| |
| symlink = i->name; |
| source_file = format_string("%s/gpio%d/value", GPIO_ROOT, |
| i->pin_number + gpio.base()); |
| CreateSymLink(source_file, symlink); |
| |
| symlink = i->name + ".attr"; |
| source_file = format_string("%s/GPIO.%d/GPIO.%s", |
| acpi_root, |
| i->index, |
| GPIO_ATTRIBUTES_EXTENSION); |
| CreateSymLink(source_file, symlink); |
| } |
| } |
| |
| static const char *__name__ = ""; |
| static void usage_help_exit(int ret) { |
| printf( |
| "Usage: %s [options]\n" |
| "\n" |
| "Options:\n" |
| " -h, --help \t show this help message and exit\n" |
| " --symlink_root=SYMLINK_ROOT\n" |
| " --pci_address=PCI_ADDRESS\n" |
| " --acpi_root=ACPI_ROOT\n", __name__); |
| exit(ret); |
| } |
| |
| int main(int argc, char *argv[]) { |
| __name__ = argv[0]; |
| |
| struct GpioSetupOptions { |
| string pci_address, |
| symlink_root, |
| acpi_root; |
| } cmd_line_options; |
| |
| // copy default values |
| cmd_line_options.pci_address = DEFAULT_GPIO_DEVICE_PCI_ADDRESS; |
| cmd_line_options.symlink_root = DEFAULT_SYMLINK_ROOT; |
| cmd_line_options.acpi_root = DEFAULT_ACPI_ROOT; |
| |
| // ProcessOptions(); |
| const struct option longopts[] = { |
| { "pci_address", 1, NULL, 'p' }, |
| { "symlink_root", 1, NULL, 's' }, |
| { "acpi_root", 1, NULL, 'a' }, |
| { "help", 0, NULL, 'h'}, |
| { 0 }, |
| }; |
| |
| int optc; |
| while ((optc = getopt_long(argc, argv, "h", longopts, NULL)) != -1) { |
| switch (optc) { |
| case 'p': |
| cmd_line_options.pci_address = optarg; |
| break; |
| |
| case 's': |
| cmd_line_options.symlink_root = optarg; |
| break; |
| |
| case 'a': |
| cmd_line_options.acpi_root = optarg; |
| break; |
| |
| default: |
| usage_help_exit(1); |
| break; |
| } |
| } |
| |
| // currently no other non-dashed arguments allowed. |
| if (optind != argc) |
| usage_help_exit(1); |
| |
| GpioChip gpioc(cmd_line_options.pci_address); |
| gpioc.Attach(); |
| CreateGpioSymlinks( |
| ParseAcpiMappings(cmd_line_options.acpi_root), |
| gpioc, |
| cmd_line_options.acpi_root.c_str(), |
| cmd_line_options.symlink_root.c_str()); |
| return 0; |
| } |