Skip to content

Instantly share code, notes, and snippets.

@samuelsadok
Created January 11, 2020 11:13
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samuelsadok/5f0ef2f366036a950b4d6dc63bfb8233 to your computer and use it in GitHub Desktop.
Save samuelsadok/5f0ef2f366036a950b4d6dc63bfb8233 to your computer and use it in GitHub Desktop.
Userspace tool for accessing GPIOs on Intel platforms
/*
* 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