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
efi_pci_disable_bridge_busmaster(void)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)
31793473c2SArvind Sankar 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) {
38793473c2SArvind Sankar 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) {
45793473c2SArvind Sankar 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
72*40262299SJoe Perches * may use it in their implementation 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)
109793473c2SArvind Sankar 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