# GPIO on Skylake/Kabylake PCH
* From Intel EDS Doc# 568693, the GPIO registers can be accessed via (SBREG_BAR + PortID + Register Offset)



* Check SBREG_BAR via P2SB Bridge (D31:F1) offset 10h


* BIOS hides P2SB Bridge registers by default so we need to unhide it by clear bit 8 of P2SB Control (P2SBC) offset E0h

* 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`