# GPIO on Skylake/Kabylake PCH * From Intel EDS Doc# 568693, the GPIO registers can be accessed via (SBREG_BAR + PortID + Register Offset) ![](https://i.imgur.com/vZayIl3.png) ![](https://i.imgur.com/R39ALz2.png) ![](https://i.imgur.com/69Y5hzv.png) * Check SBREG_BAR via P2SB Bridge (D31:F1) offset 10h ![](https://i.imgur.com/hyK6KW0.png) ![](https://i.imgur.com/rFnbMHR.png) * BIOS hides P2SB Bridge registers by default so we need to unhide it by clear bit 8 of P2SB Control (P2SBC) offset E0h ![](https://i.imgur.com/k15dYAZ.png) * Access Flow * Get SBREG_BAR from B0/D31/F1 offset 10h, unhide P2SB (clear bit0 of B0/D31/F1 offset E1h) if needed. * PortID = 0xAF - port, i.e. 0xAF for GPIO Community 0, 0xAE for GPIO Community 1 and so on... (not documented, found from [Coreboot source code](https://chromium.googlesource.com/chromiumos/third_party/coreboot/+/chromeos-2013.04/src/soc/intel/skylake/include/soc/pcr.h) and [witequark's lab notebook](https://lab.whitequark.org/notes/2017-11-08/accessing-intel-ich-pch-gpios/)) * Use SBREG_BAR + (PortID << 16)+ Register Offset to read/write the configuration registers * Get Pad Base Address (PADBAR) offset Ch, defualt value is 400h * Pad Configuration DW0 * GPIO: bit[11:10] = 0h * Input: bit[9] = 0h, bit[1] = RX State * Output: bit[8] = 0h, bit[0] = TX state * Pad Configuration DW1 * Termination mode: bit[13:10] * Sample code from [witequark](https://lab.whitequark.org/notes/2017-11-08/accessing-intel-ich-pch-gpios/) ```clike= 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) { MSG("Is your kernel configured with CONFIG_DEVMEM_STRICT=n?"); MSG("Try rebooting and specifying iomem=relaxed on kernel command line."); } 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); } } 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; } ``` ###### tags: `Intel` `Linux` `GPIO`