Created
March 30, 2018 01:07
-
-
Save kylemanna/55fabfe80b76fc640b2a59aa6e7ac9b4 to your computer and use it in GitHub Desktop.
Read Intel PCH GPIOs and Display Interrupt Enable status
This file contains hidden or 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
/* | |
* 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) | |
* - Original source: https://lab.whitequark.org/notes/2017-11-08/accessing-intel-ich-pch-gpios/ | |
*/ | |
#include <stdio.h> | |
#include <stdint.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[4][3] = { | |
{0x00, 0x30, 0x40}, /* USE_SEL[1-3] offsets */ | |
{0x04, 0x34, 0x44}, /* IO_SEL[1-3] offsets */ | |
{0x0c, 0x38, 0x48}, /* LVL[1-3] offsets */ | |
}; | |
/* | |
* Generic PCI configuration space registers. | |
*/ | |
#define REG_VENDOR 0x00 | |
#define REG_DEVICE 0x04 | |
/* | |
* 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 | |
/* | |
* D31:F1 configuration space registers. | |
*/ | |
#define REG_P2SB_BAR 0x10 | |
#define REG_P2SB_BARH 0x14 | |
#define REG_P2SB_CTRL 0xe0 | |
#define REG_P2SB_CTRL_HIDE 0x0100 | |
/* | |
* P2SB private registers. | |
*/ | |
#define P2SB_PORTID_SHIFT 16 | |
#define P2SB_PORT_GPIO3 0xAC | |
#define P2SB_PORT_GPIO2 0xAD | |
#define P2SB_PORT_GPIO1 0xAE | |
#define P2SB_PORT_GPIO0 0xAF | |
/* | |
* GPIO sideband registers. | |
*/ | |
#define REG_PCH_GPIO_FAMBAR 0x8 | |
#define REG_PCH_GPIO_PADBAR 0xc | |
#define REG_PCH_GPIO_PAD_OWN 0x20 | |
#define REG_PCH_GPIO_HOSTSW_OWN 0xd0 | |
#define REG_PCH_GPIO_GPI_IS 0x100 | |
#define REG_PCH_GPIO_GPI_IE 0x120 | |
#define REG_PCH_GPIO_GPE_STS 0x140 | |
#define REG_PCH_GPIO_GPE_EN 0x160 | |
#define REG_PCH_GPIO_SMI_STS 0x184 | |
#define REG_PCH_GPIO_SMI_EN 0x1a4 | |
#define REG_PCH_GPIO_NMI_STS 0x1c4 | |
#define REG_PCH_GPIO_NMI_EN 0x1e4 | |
#define REG_PCH_GPIO_DW0_PMODE 0x1600 | |
#define REG_PCH_GPIO_DW0_RXDIS 0x0200 | |
#define REG_PCH_GPIO_DW0_TXDIS 0x0100 | |
#define REG_PCH_GPIO_DW0_RXSTATE 0x0002 | |
#define REG_PCH_GPIO_DW0_TXSTATE 0x0001 | |
#define REG_PCH_GPIO_DW1_TERM_NONE 0x0 | |
#define REG_PCH_GPIO_DW1_TERM_5K_PD 0x2 | |
#define REG_PCH_GPIO_DW1_TERM_20K_PD 0x4 | |
#define REG_PCH_GPIO_DW1_TERM_5K_PU 0xa | |
#define REG_PCH_GPIO_DW1_TERM_20K_PU 0xc | |
#define REG_PCH_GPIO_DW1_TERM_NATIVE 0xf | |
/* | |
* 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; | |
} | |
/* | |
* Finally, our main logic! | |
*/ | |
int try_ich(struct pci_access *pci, | |
uint16_t reg_gpiobase, uint16_t reg_gc, | |
const char *desc, int *fatal) { | |
MSG("Checking for a %s system", desc); | |
struct pci_dev *d31f0 = pci_find_dev(pci, 0, 31, 0); | |
uint32_t 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) { | |
*fatal = 1; | |
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)) { | |
*fatal = 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); | |
} | |
for(int n = 1; n < 3; n++) { | |
MSG("USE_SEL%d=%08x", n, inl(gpiobase + ichx_regs[GPIO_USE_SEL][n])); | |
MSG("IO_SEL%d=%08x", n, inl(gpiobase + ichx_regs[GPIO_IO_SEL][n])); | |
MSG("LVL%d=%08x", n, inl(gpiobase + ichx_regs[GPIO_LVL][n])); | |
} | |
return 0; | |
} | |
int get_pch_sbreg_addr(struct pci_access *pci, pciaddr_t *sbreg_addr) { | |
MSG("Checking for a Series 10 PCH system"); | |
struct pci_dev *d31f1 = pci_get_dev(pci, 0, 0, 31, 1); | |
pci_fill_info(d31f1, PCI_FILL_IDENT); | |
if(d31f1->vendor_id == 0xffff) { | |
MSG("Cannot find D31:F1, assuming it is hidden by firmware"); | |
uint32_t p2sb_ctrl = pci_read_long(d31f1, REG_P2SB_CTRL); | |
MSG("P2SB_CTRL=%02x", p2sb_ctrl); | |
if(!(p2sb_ctrl & REG_P2SB_CTRL_HIDE)) { | |
ERR("D31:F1 is hidden but P2SB_E1 is not 0xff, bailing out"); | |
} | |
MSG("Unhiding P2SB"); | |
pci_write_long(d31f1, REG_P2SB_CTRL, p2sb_ctrl & ~REG_P2SB_CTRL_HIDE); | |
p2sb_ctrl = pci_read_long(d31f1, REG_P2SB_CTRL); | |
MSG("P2SB_CTRL=%02x", p2sb_ctrl); | |
if(p2sb_ctrl & REG_P2SB_CTRL_HIDE) { | |
ERR("Cannot unhide PS2B"); | |
} | |
pci_fill_info(d31f1, PCI_FILL_RESCAN | PCI_FILL_IDENT); | |
if(d31f1->vendor_id == 0xffff) { | |
ERR("P2SB unhidden but does not enumerate, bailing out"); | |
} | |
} | |
pci_fill_info(d31f1, PCI_FILL_RESCAN | PCI_FILL_IDENT | PCI_FILL_BASES); | |
if(d31f1->vendor_id != 0x8086) { | |
ERR("Vendor of D31:F1 is not Intel"); | |
} else if((uint32_t)d31f1->base_addr[0] == 0xffffffff) { | |
ERR("SBREG_BAR is not implemented in D31:F1"); | |
} | |
*sbreg_addr = d31f1->base_addr[0] &~ 0xf; | |
MSG("SBREG_ADDR=%08lx", *sbreg_addr); | |
MSG("Hiding P2SB again"); | |
uint32_t p2sb_ctrl = pci_read_long(d31f1, REG_P2SB_CTRL); | |
pci_write_long(d31f1, REG_P2SB_CTRL, p2sb_ctrl | REG_P2SB_CTRL_HIDE); | |
pci_fill_info(d31f1, PCI_FILL_RESCAN | PCI_FILL_IDENT); | |
if(d31f1->vendor_id != 0xffff) { | |
ERR("Cannot hide P2SB"); | |
} | |
return 0; | |
} | |
uint32_t sideband_read(void *sbmap, uint8_t port, uint16_t reg) { | |
uintptr_t addr = ((uintptr_t)sbmap + (port << P2SB_PORTID_SHIFT) + reg); | |
return *((volatile uint32_t *)addr); | |
} | |
int try_pch(struct pci_access *pci) { | |
pciaddr_t sbreg_addr; | |
if(get_pch_sbreg_addr(pci, &sbreg_addr)) { | |
MSG("Re-enumerating PCI devices will probably crash the system"); | |
ERR("Probing Series 100 PCH failed"); | |
} | |
int memfd = open("/dev/mem", O_RDWR); | |
if(memfd == -1) { | |
ERR("Cannot open /dev/mem"); | |
} | |
void *sbmap = mmap((void*)sbreg_addr, 1<<24, PROT_READ|PROT_WRITE, MAP_SHARED, | |
memfd, sbreg_addr); | |
if(sbmap == MAP_FAILED) { | |
if(errno == EPERM) { | |
// The requirement might be relaxed to CONFIG_IO_DEVMEM_STRICT=n, but I'm not sure. | |
MSG("Is your kernel configured with CONFIG_DEVMEM_STRICT=n?"); | |
} | |
ERR("Cannot map SBREG"); | |
} | |
close(memfd); | |
for(unsigned port = 0; port < 4; port++) { | |
uint16_t port_id = P2SB_PORT_GPIO0 - port; | |
uint32_t padbar = sideband_read(sbmap, port_id, REG_PCH_GPIO_PADBAR); | |
MSG("GPIO%d_PADBAR=%x", port, padbar); | |
for(unsigned pad = 0; pad < 32; pad++) { | |
uint32_t dw0 = sideband_read(sbmap, port_id, padbar + pad * 8); | |
uint32_t dw1 = sideband_read(sbmap, port_id, padbar + pad * 8 + 4); | |
if(dw1 == 0) { | |
// Not documented as such, but appears to be a reliable last pad marker. | |
break; | |
} | |
const char *state = "???", *rxstate = "", *txstate = ""; | |
if((dw0 & REG_PCH_GPIO_DW0_PMODE) != 0) { | |
state = "Native"; | |
} else if((dw0 & REG_PCH_GPIO_DW0_TXDIS) != 0 && | |
(dw0 & REG_PCH_GPIO_DW0_RXDIS) != 0) { | |
state = "Off"; | |
} else { | |
state = "GPIO"; | |
if((dw0 & REG_PCH_GPIO_DW0_RXDIS) == 0) { | |
if((dw0 & REG_PCH_GPIO_DW0_RXSTATE) != 0) { | |
rxstate = " InHigh"; | |
} else { | |
rxstate = " InLow"; | |
} | |
} | |
if((dw0 & REG_PCH_GPIO_DW0_TXDIS) == 0) { | |
if((dw0 & REG_PCH_GPIO_DW0_TXSTATE) != 0) { | |
txstate = " OutHigh"; | |
} else { | |
txstate = " OutLow"; | |
} | |
} | |
} | |
const char *pull = "???"; | |
switch(dw1 >> 10) { | |
case REG_PCH_GPIO_DW1_TERM_NONE: pull = "None"; break; | |
case REG_PCH_GPIO_DW1_TERM_5K_PD: pull = "Dn5k"; break; | |
case REG_PCH_GPIO_DW1_TERM_20K_PD: pull = "Dn20k"; break; | |
case REG_PCH_GPIO_DW1_TERM_5K_PU: pull = "Up5k"; break; | |
case REG_PCH_GPIO_DW1_TERM_20K_PU: pull = "Up20k"; break; | |
case REG_PCH_GPIO_DW1_TERM_NATIVE: pull = "Native"; break; | |
} | |
printf("[+] GPIO%d_PAD%d: DW0=%08x DW1=%08x State=%s%s%s Pull=%s\n", | |
port, pad, dw0, dw1, state, rxstate, txstate, pull); | |
} | |
} | |
//uint32_t is_reg = sideband_read(sbmap, port_id, REG_PCH_GPIO_GPI_IS); | |
MSG("Interrupt Enable Registers:"); | |
printf("[+] GPI_IE_GPP_A = 0x%08x\n", sideband_read(sbmap, P2SB_PORT_GPIO0-0, REG_PCH_GPIO_GPI_IE + 0x00)); | |
printf("[+] GPI_IE_GPP_B = 0x%08x\n", sideband_read(sbmap, P2SB_PORT_GPIO0-0, REG_PCH_GPIO_GPI_IE + 0x04)); | |
printf("[+] GPI_IE_GPP_C = 0x%08x\n", sideband_read(sbmap, P2SB_PORT_GPIO0-1, REG_PCH_GPIO_GPI_IE + 0x00)); | |
printf("[+] GPI_IE_GPP_D = 0x%08x\n", sideband_read(sbmap, P2SB_PORT_GPIO0-1, REG_PCH_GPIO_GPI_IE + 0x04)); | |
printf("[+] GPI_IE_GPP_E = 0x%08x\n", sideband_read(sbmap, P2SB_PORT_GPIO0-1, REG_PCH_GPIO_GPI_IE + 0x08)); | |
printf("[+] GPI_IE_GPP_F = 0x%08x\n", sideband_read(sbmap, P2SB_PORT_GPIO0-1, REG_PCH_GPIO_GPI_IE + 0x0c)); | |
printf("[+] GPI_IE_GPP_G = 0x%08x\n", sideband_read(sbmap, P2SB_PORT_GPIO0-1, REG_PCH_GPIO_GPI_IE + 0x10)); | |
printf("[+] GPI_IE_GPP_H = 0x%08x\n", sideband_read(sbmap, P2SB_PORT_GPIO0-1, REG_PCH_GPIO_GPI_IE + 0x14)); | |
printf("[+] GPI_IE_GPP_I = 0x%08x\n", sideband_read(sbmap, P2SB_PORT_GPIO0-3, REG_PCH_GPIO_GPI_IE + 0x00)); | |
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); | |
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"); | |
} | |
*pci_out = pci; | |
return 0; | |
} | |
int main() { | |
struct pci_access *pci; | |
if(create_pci(PCI_ACCESS_AUTO, &pci)) { | |
MSG("Is this an Intel platform?"); | |
return 1; | |
} | |
int fatal = 0; | |
if(try_ich(pci, REG_ICH0_GPIOBASE, REG_ICH0_GC, | |
"ICH0..ICH5", &fatal) && fatal) { | |
return 1; | |
} else if(try_ich(pci, REG_ICH6_GPIOBASE, REG_ICH6_GC, | |
"ICH6..ICH9 or Series 5..9 PCH", &fatal) && fatal) { | |
return 1; | |
} else { | |
pci_cleanup(pci); | |
// Letting Linux discover P2SB (and reassign its BAR) hangs the system, | |
// so we need to enumerate the device bypassing it. | |
if(create_pci(PCI_ACCESS_I386_TYPE1, &pci)) { | |
return 1; | |
} | |
if(try_pch(pci)) { | |
return 1; | |
} | |
} | |
printf("[+] Done\n"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The correct Makefile is this and not the one in your page: