14444f854SMatthew Garrett // SPDX-License-Identifier: GPL-2.0 24444f854SMatthew Garrett /* 34444f854SMatthew Garrett * PCI-related functions used by the EFI stub on multiple 44444f854SMatthew Garrett * architectures. 54444f854SMatthew Garrett * 64444f854SMatthew Garrett * Copyright 2019 Google, LLC 74444f854SMatthew Garrett */ 84444f854SMatthew Garrett 94444f854SMatthew Garrett #include <linux/efi.h> 104444f854SMatthew Garrett #include <linux/pci.h> 114444f854SMatthew Garrett 124444f854SMatthew Garrett #include <asm/efi.h> 134444f854SMatthew Garrett 144444f854SMatthew Garrett #include "efistub.h" 154444f854SMatthew Garrett 164444f854SMatthew Garrett void efi_pci_disable_bridge_busmaster(void) 174444f854SMatthew Garrett { 184444f854SMatthew Garrett efi_guid_t pci_proto = EFI_PCI_IO_PROTOCOL_GUID; 194444f854SMatthew Garrett unsigned long pci_handle_size = 0; 204444f854SMatthew Garrett efi_handle_t *pci_handle = NULL; 214444f854SMatthew Garrett efi_handle_t handle; 224444f854SMatthew Garrett efi_status_t status; 234444f854SMatthew Garrett u16 class, command; 244444f854SMatthew Garrett int i; 254444f854SMatthew Garrett 264444f854SMatthew Garrett status = efi_bs_call(locate_handle, EFI_LOCATE_BY_PROTOCOL, &pci_proto, 274444f854SMatthew Garrett NULL, &pci_handle_size, NULL); 284444f854SMatthew Garrett 294444f854SMatthew Garrett if (status != EFI_BUFFER_TOO_SMALL) { 304444f854SMatthew Garrett if (status != EFI_SUCCESS && status != EFI_NOT_FOUND) 314444f854SMatthew Garrett pr_efi_err("Failed to locate PCI I/O handles'\n"); 324444f854SMatthew Garrett return; 334444f854SMatthew Garrett } 344444f854SMatthew Garrett 354444f854SMatthew Garrett status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, pci_handle_size, 364444f854SMatthew Garrett (void **)&pci_handle); 374444f854SMatthew Garrett if (status != EFI_SUCCESS) { 384444f854SMatthew Garrett pr_efi_err("Failed to allocate memory for 'pci_handle'\n"); 394444f854SMatthew Garrett return; 404444f854SMatthew Garrett } 414444f854SMatthew Garrett 424444f854SMatthew Garrett status = efi_bs_call(locate_handle, EFI_LOCATE_BY_PROTOCOL, &pci_proto, 434444f854SMatthew Garrett NULL, &pci_handle_size, pci_handle); 444444f854SMatthew Garrett if (status != EFI_SUCCESS) { 454444f854SMatthew Garrett pr_efi_err("Failed to locate PCI I/O handles'\n"); 464444f854SMatthew Garrett goto free_handle; 474444f854SMatthew Garrett } 484444f854SMatthew Garrett 494444f854SMatthew Garrett for_each_efi_handle(handle, pci_handle, pci_handle_size, i) { 504444f854SMatthew Garrett efi_pci_io_protocol_t *pci; 514444f854SMatthew Garrett unsigned long segment_nr, bus_nr, device_nr, func_nr; 524444f854SMatthew Garrett 534444f854SMatthew Garrett status = efi_bs_call(handle_protocol, handle, &pci_proto, 544444f854SMatthew Garrett (void **)&pci); 554444f854SMatthew Garrett if (status != EFI_SUCCESS) 564444f854SMatthew Garrett continue; 574444f854SMatthew Garrett 584444f854SMatthew Garrett /* 594444f854SMatthew Garrett * Disregard devices living on bus 0 - these are not behind a 604444f854SMatthew Garrett * bridge so no point in disconnecting them from their drivers. 614444f854SMatthew Garrett */ 624444f854SMatthew Garrett status = efi_call_proto(pci, get_location, &segment_nr, &bus_nr, 634444f854SMatthew Garrett &device_nr, &func_nr); 644444f854SMatthew Garrett if (status != EFI_SUCCESS || bus_nr == 0) 654444f854SMatthew Garrett continue; 664444f854SMatthew Garrett 674444f854SMatthew Garrett /* 684444f854SMatthew Garrett * Don't disconnect VGA controllers so we don't risk losing 694444f854SMatthew Garrett * access to the framebuffer. Drivers for true PCIe graphics 704444f854SMatthew Garrett * controllers that are behind a PCIe root port do not use 714444f854SMatthew Garrett * DMA to implement the GOP framebuffer anyway [although they 724444f854SMatthew Garrett * may use it in their implentation of Gop->Blt()], and so 734444f854SMatthew Garrett * disabling DMA in the PCI bridge should not interfere with 744444f854SMatthew Garrett * normal operation of the device. 754444f854SMatthew Garrett */ 764444f854SMatthew Garrett status = efi_call_proto(pci, pci.read, EfiPciIoWidthUint16, 774444f854SMatthew Garrett PCI_CLASS_DEVICE, 1, &class); 784444f854SMatthew Garrett if (status != EFI_SUCCESS || class == PCI_CLASS_DISPLAY_VGA) 794444f854SMatthew Garrett continue; 804444f854SMatthew Garrett 814444f854SMatthew Garrett /* Disconnect this handle from all its drivers */ 824444f854SMatthew Garrett efi_bs_call(disconnect_controller, handle, NULL, NULL); 834444f854SMatthew Garrett } 844444f854SMatthew Garrett 854444f854SMatthew Garrett for_each_efi_handle(handle, pci_handle, pci_handle_size, i) { 864444f854SMatthew Garrett efi_pci_io_protocol_t *pci; 874444f854SMatthew Garrett 884444f854SMatthew Garrett status = efi_bs_call(handle_protocol, handle, &pci_proto, 894444f854SMatthew Garrett (void **)&pci); 904444f854SMatthew Garrett if (status != EFI_SUCCESS || !pci) 914444f854SMatthew Garrett continue; 924444f854SMatthew Garrett 934444f854SMatthew Garrett status = efi_call_proto(pci, pci.read, EfiPciIoWidthUint16, 944444f854SMatthew Garrett PCI_CLASS_DEVICE, 1, &class); 954444f854SMatthew Garrett 964444f854SMatthew Garrett if (status != EFI_SUCCESS || class != PCI_CLASS_BRIDGE_PCI) 974444f854SMatthew Garrett continue; 984444f854SMatthew Garrett 994444f854SMatthew Garrett /* Disable busmastering */ 1004444f854SMatthew Garrett status = efi_call_proto(pci, pci.read, EfiPciIoWidthUint16, 1014444f854SMatthew Garrett PCI_COMMAND, 1, &command); 1024444f854SMatthew Garrett if (status != EFI_SUCCESS || !(command & PCI_COMMAND_MASTER)) 1034444f854SMatthew Garrett continue; 1044444f854SMatthew Garrett 1054444f854SMatthew Garrett command &= ~PCI_COMMAND_MASTER; 1064444f854SMatthew Garrett status = efi_call_proto(pci, pci.write, EfiPciIoWidthUint16, 1074444f854SMatthew Garrett PCI_COMMAND, 1, &command); 1084444f854SMatthew Garrett if (status != EFI_SUCCESS) 1094444f854SMatthew Garrett pr_efi_err("Failed to disable PCI busmastering\n"); 1104444f854SMatthew Garrett } 1114444f854SMatthew Garrett 1124444f854SMatthew Garrett free_handle: 1134444f854SMatthew Garrett efi_bs_call(free_pool, pci_handle); 1144444f854SMatthew Garrett } 115