Created
January 11, 2020 11:13
-
-
Save samuelsadok/5f0ef2f366036a950b4d6dc63bfb8233 to your computer and use it in GitHub Desktop.
Userspace tool for accessing GPIOs on Intel platforms
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Note: This code is mostly taken from Whitequark's Lab Notebook: | |
* https://lab.whitequark.org/notes/2017-11-08/accessing-intel-ich-pch-gpios/ | |
* | |
* Compile with: | |
* gcc -std=c11 -lpci -Wall -g -o gpioke gpioke.c | |
* | |
* Usage Example: | |
* sudo ./gpioke --set-dir 10 1 --set-level 10 0 --sleep 1000 --set-level 10 1 | |
* | |
* This configures GPIO10 as output, sets it to 0 for 1 second and then to 1. | |
* | |
* Caution: This can probably damage your hardware if not used carefully. Refer | |
* to your schematics when in doubt. | |
*/ | |
/* | |
* See: | |
* - http://lxr.free-electrons.com/source/drivers/mfd/lpc_ich.c | |
* - http://lxr.free-electrons.com/source/drivers/gpio/gpio-ich.c | |
* - Intel document 252516-001 (ICH5) | |
* - Intel document 330550-002 (9 Series PCH) | |
* - Intel documents 332690-004 and 332691-002EN (100 Series PCH) | |
*/ | |
#define _DEFAULT_SOURCE // required for usleep | |
#include <stdio.h> | |
#include <stdint.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include <pci/pci.h> | |
#include <sys/io.h> | |
#include <sys/mman.h> | |
#include <sys/errno.h> | |
/* | |
* GPIO register offsets in GPIO I/O space. | |
* Each chunk of 32 GPIOs is manipulated via its own USE_SELx, IO_SELx, and | |
* LVLx registers. Logic in the read/write functions takes a register and | |
* an absolute bit number and determines the proper register offset and bit | |
* number in that register. For example, to read the value of GPIO bit 50 | |
* the code would access offset ichx_regs[2(=GPIO_LVL)][1(=50/32)], | |
* bit 18 (50%32). | |
*/ | |
enum GPIO_REG { | |
GPIO_USE_SEL = 0, | |
GPIO_IO_SEL, | |
GPIO_LVL, | |
}; | |
static const uint8_t ichx_regs[3][3] = { | |
{0x00, 0x30, 0x40}, /* USE_SEL[1-3] offsets */ | |
{0x04, 0x34, 0x44}, /* IO_SEL[1-3] offsets */ | |
{0x0c, 0x38, 0x48}, /* LVL[1-3] offsets */ | |
}; | |
/* | |
* D31:F0 configuration space registers. | |
*/ | |
#define REG_ICH0_GPIOBASE 0x58 | |
#define REG_ICH0_GC 0x5c | |
#define REG_ICH6_GPIOBASE 0x48 | |
#define REG_ICH6_GC 0x4c | |
#define REG_ICHx_GC_EN 0x10 | |
#define REG_ICHx_GC_GLE 0x01 | |
/* | |
* Helper functions. | |
*/ | |
#define MSG(...) do { \ | |
fprintf(stderr, "[*] " __VA_ARGS__); fprintf(stderr, "\n"); \ | |
} while(0) | |
#define ERR(...) do { \ | |
fprintf(stderr, "[-] " __VA_ARGS__); fprintf(stderr, "\n"); \ | |
return 1; \ | |
} while(0) | |
#define DIE(...) do { *fatal = 1; ERR(__VA_ARGS__) } while(0) | |
struct pci_dev *pci_find_dev(struct pci_access *pci, uint8_t bus, uint8_t dev, uint8_t func) { | |
for (struct pci_dev *it = pci->devices; it; it = it->next) { | |
if (it->bus == bus && it->dev == dev && it->func == func) return it; | |
} | |
return NULL; | |
} | |
void dump_gpio(uint16_t gpiobase, uint32_t gpio_num) { | |
uint32_t reg_num = gpio_num >> 5; | |
uint32_t bit_num = gpio_num & 0x1f; | |
uint32_t use_sel = (inl(gpiobase + ichx_regs[GPIO_USE_SEL][reg_num]) >> bit_num) & 1; | |
uint32_t io_sel = (inl(gpiobase + ichx_regs[GPIO_IO_SEL][reg_num]) >> bit_num) & 1; | |
uint32_t lvl = (inl(gpiobase + ichx_regs[GPIO_LVL][reg_num]) >> bit_num) & 1; | |
if (use_sel) { | |
printf("GPIO%d: %s, %s\n", gpio_num, io_sel ? "input" : "output", lvl ? "high" : "low"); | |
} else { | |
printf("GPIO%d: not a GPIO\n", gpio_num); | |
} | |
} | |
void gpio_set_dir(uint16_t gpiobase, uint32_t gpio_num, int output) { | |
uint32_t reg_num = gpio_num >> 5; | |
uint32_t bit_num = gpio_num & 0x1f; | |
uint32_t use_sel = inl(gpiobase + ichx_regs[GPIO_USE_SEL][reg_num]); | |
outl(use_sel | (1 << bit_num), gpiobase + ichx_regs[GPIO_USE_SEL][reg_num]); | |
uint32_t io_sel = inl(gpiobase + ichx_regs[GPIO_IO_SEL][reg_num]); | |
if (output) { | |
outl(io_sel & ~(1 << bit_num), gpiobase + ichx_regs[GPIO_IO_SEL][reg_num]); | |
} else { | |
outl(io_sel | (1 << bit_num), gpiobase + ichx_regs[GPIO_IO_SEL][reg_num]); | |
} | |
} | |
void gpio_write(uint16_t gpiobase, uint32_t gpio_num, int level) { | |
uint32_t reg_num = gpio_num >> 5; | |
uint32_t bit_num = gpio_num & 0x1f; | |
uint32_t lvl = inl(gpiobase + ichx_regs[GPIO_LVL][reg_num]); | |
if (level) { | |
outl(lvl | (1 << bit_num), gpiobase + ichx_regs[GPIO_LVL][reg_num]); | |
} else { | |
outl(lvl & ~(1 << bit_num), gpiobase + ichx_regs[GPIO_LVL][reg_num]); | |
} | |
} | |
/** | |
* @brief Try to init ICH | |
*/ | |
int try_ich(struct pci_access *pci, | |
uint16_t reg_gpiobase, uint16_t reg_gc, | |
uint32_t* gpiobase) { | |
struct pci_dev *d31f0 = pci_find_dev(pci, 0, 31, 0); | |
if (!d31f0) { | |
ERR("Cannot find D31:F0"); | |
} | |
pci_fill_info(d31f0, PCI_FILL_IDENT | PCI_FILL_BASES); | |
if (d31f0->vendor_id != 0x8086) { | |
ERR("Vendor of D31:F0 is not Intel"); | |
} | |
*gpiobase = pci_read_long(d31f0, reg_gpiobase); | |
uint8_t gc = pci_read_byte(d31f0, reg_gc); | |
MSG("GPIOBASE=%08x, GC=%02x", *gpiobase, gc); | |
if (*gpiobase == 0xffffffff) { | |
ERR("Cannot read GPIOBASE, are you running me as root?"); | |
} else if (*gpiobase == 0) { | |
ERR("GPIOBASE not implemented at %04x", reg_gpiobase); | |
} else if (!(*gpiobase & 1)) { | |
ERR("GPIOBASE is not an I/O BAR"); | |
} | |
if (!(*gpiobase & 0xfffc)) { | |
const uint32_t DEFAULT_GPIOBASE = 0x0480; | |
MSG("GPIOBASE is not configured, setting to %08x and hoping this works", DEFAULT_GPIOBASE); | |
pci_write_long(d31f0, reg_gpiobase, DEFAULT_GPIOBASE); | |
*gpiobase = pci_read_long(d31f0, reg_gpiobase); | |
if ((*gpiobase & 0xfffc) != DEFAULT_GPIOBASE) { | |
ERR("Cannot set GPIOBASE"); | |
} | |
} | |
MSG("GPIO decoding is %s", (gc & REG_ICHx_GC_EN) ? "enabled" : "disabled"); | |
MSG("GPIO lockdown is %s", (gc & REG_ICHx_GC_GLE) ? "enabled" : "disabled"); | |
if (!(gc & REG_ICHx_GC_EN)) { | |
MSG("Enabling GPIO decoding"); | |
pci_write_byte(d31f0, reg_gc, gc | REG_ICHx_GC_EN); | |
gc = pci_read_byte(d31f0, reg_gc); | |
if (!(gc & REG_ICHx_GC_EN)) { | |
ERR("Cannot enable GPIO decoding"); | |
} | |
} | |
*gpiobase &= 0xfffc; | |
if (ioperm(*gpiobase, 128, 1) == -1) { | |
ERR("Cannot access I/O ports %04x:%04x", *gpiobase, *gpiobase + 128); | |
} | |
return 0; | |
} | |
int create_pci(int method, struct pci_access **pci_out) { | |
struct pci_access *pci = pci_alloc(); | |
pci->method = method; | |
pci_init(pci); | |
pci_scan_bus(pci); | |
*pci_out = pci; | |
return 0; | |
} | |
int main(int argc, const char ** argv) { | |
struct pci_access *pci; | |
if(create_pci(PCI_ACCESS_AUTO, &pci)) { | |
MSG("Is this an Intel platform?"); | |
return 1; | |
} | |
uint32_t gpiobase = 0; | |
MSG("Trying to init ICH0..ICH5..."); | |
if (try_ich(pci, REG_ICH0_GPIOBASE, REG_ICH0_GC, &gpiobase)) { | |
MSG("Trying to init ICH6..ICH9 or Series 5..9 PCH..."); | |
if (try_ich(pci, REG_ICH6_GPIOBASE, REG_ICH6_GC, &gpiobase)) { | |
ERR("Unable to init ICH"); | |
return 1; | |
} | |
} | |
for (int n = 0; n < 3; n++) { | |
MSG("USE_SEL%d=%08x", n + 1, inl(gpiobase + ichx_regs[GPIO_USE_SEL][n])); | |
MSG("IO_SEL%d=%08x", n + 1, inl(gpiobase + ichx_regs[GPIO_IO_SEL][n])); | |
MSG("LVL%d=%08x", n + 1, inl(gpiobase + ichx_regs[GPIO_LVL][n])); | |
} | |
if (argc) { | |
argc--; | |
argv++; | |
} | |
// Execute command line args | |
while (argc) { | |
if (strcmp(argv[0], "--set-dir") == 0) { | |
if (argc < 3) { | |
ERR("invalid usage"); | |
break; | |
} | |
int gpio_num = strtol(argv[1], NULL, 0); | |
int gpio_dir = strtol(argv[2], NULL, 0); | |
argc -= 2; | |
argv += 2; | |
printf("[+] configuring GPIO %d as %s\n", gpio_num, gpio_dir ? "output" : "input"); | |
gpio_set_dir(gpiobase, gpio_num, gpio_dir); | |
} else if (strcmp(argv[0], "--set-level") == 0) { | |
if (argc < 3) { | |
ERR("invalid usage"); | |
break; | |
} | |
int gpio_num = strtol(argv[1], NULL, 0); | |
int gpio_level = strtol(argv[2], NULL, 0); | |
argc -= 2; | |
argv += 2; | |
printf("[+] setting GPIO %d to %s\n", gpio_num, gpio_level ? "high" : "low"); | |
gpio_write(gpiobase, gpio_num, gpio_level); | |
} else if (strcmp(argv[0], "--sleep") == 0) { | |
if (argc < 2) { | |
ERR("invalid usage"); | |
break; | |
} | |
int duration_ms = strtol(argv[1], NULL, 0); | |
argc--; | |
argv++; | |
printf("[+] sleeping\n"); | |
usleep(duration_ms * 1000ULL); | |
} else if (strcmp(argv[0], "--dump") == 0) { | |
// TODO: don't hardcode GPIO count | |
for (uint16_t gpio_num = 0; gpio_num < 76; ++gpio_num) { | |
dump_gpio(gpiobase, gpio_num); | |
} | |
} else { | |
ERR("invalid argument: %s", argv[0]); | |
break; | |
} | |
argc--; | |
argv++; | |
} | |
printf("[+] cleaning up...\n"); | |
pci_cleanup(pci); | |
printf("[+] Done\n"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment