Merge branch 'pci/vga'

- Move vgaarb.c from drivers/gpu/vga to drivers/pci (Bjorn Helgaas)

- Factor out default VGA device selection (Huacai Chen)

- Move firmware default device detection to ADD_DEVICE path so we can
  select a default device regardless of whether it is enumerated before or
  after vga_arb_device_init() (Huacai Chen)

- Move non-legacy VGA detection to ADD_DEVICE path (Huacai Chen)

- Move disabled VGA device detection to ADD_DEVICE path (Huacai Chen)

* pci/vga:
  PCI/VGA: Replace full MIT license text with SPDX identifier
  PCI/VGA: Use unsigned format string to print lock counts
  PCI/VGA: Log bridge control messages when adding devices
  PCI/VGA: Remove empty vga_arb_device_card_gone()
  PCI/VGA: Move disabled VGA device detection to ADD_DEVICE path
  PCI/VGA: Move non-legacy VGA detection to ADD_DEVICE path
  PCI/VGA: Move firmware default device detection to ADD_DEVICE path
  PCI/VGA: Factor out default VGA device selection
  PCI/VGA: Factor out vga_select_framebuffer_device()
  PCI/VGA: Move vga_arb_integrated_gpu() earlier in file
  PCI/VGA: Move vgaarb to drivers/pci
This commit is contained in:
Bjorn Helgaas 2022-03-22 17:16:22 -05:00
commit 816f891704
6 changed files with 176 additions and 179 deletions

View File

@ -100,7 +100,7 @@ In-kernel interface
.. kernel-doc:: include/linux/vgaarb.h
:internal:
.. kernel-doc:: drivers/gpu/vga/vgaarb.c
.. kernel-doc:: drivers/pci/vgaarb.c
:export:
libpciaccess

View File

@ -1,23 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
config VGA_ARB
bool "VGA Arbitration" if EXPERT
default y
depends on (PCI && !S390)
help
Some "legacy" VGA devices implemented on PCI typically have the same
hard-decoded addresses as they did on ISA. When multiple PCI devices
are accessed at same time they need some kind of coordination. Please
see Documentation/gpu/vgaarbiter.rst for more details. Select this to
enable VGA arbiter.
config VGA_ARB_MAX_GPUS
int "Maximum number of GPUs"
default 16
depends on VGA_ARB
help
Reserves space in the kernel to maintain resource locking for
multiple GPUS. The overhead for each GPU is very small.
config VGA_SWITCHEROO
bool "Laptop Hybrid Graphics - GPU switching support"
depends on X86

View File

@ -1,3 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_VGA_ARB) += vgaarb.o
obj-$(CONFIG_VGA_SWITCHEROO) += vga_switcheroo.o

View File

@ -252,6 +252,25 @@ config PCIE_BUS_PEER2PEER
endchoice
config VGA_ARB
bool "VGA Arbitration" if EXPERT
default y
depends on (PCI && !S390)
help
Some "legacy" VGA devices implemented on PCI typically have the same
hard-decoded addresses as they did on ISA. When multiple PCI devices
are accessed at same time they need some kind of coordination. Please
see Documentation/gpu/vgaarbiter.rst for more details. Select this to
enable VGA arbiter.
config VGA_ARB_MAX_GPUS
int "Maximum number of GPUs"
default 16
depends on VGA_ARB
help
Reserves space in the kernel to maintain resource locking for
multiple GPUS. The overhead for each GPU is very small.
source "drivers/pci/hotplug/Kconfig"
source "drivers/pci/controller/Kconfig"
source "drivers/pci/endpoint/Kconfig"

View File

@ -30,6 +30,7 @@ obj-$(CONFIG_PCI_PF_STUB) += pci-pf-stub.o
obj-$(CONFIG_PCI_ECAM) += ecam.o
obj-$(CONFIG_PCI_P2PDMA) += p2pdma.o
obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
obj-$(CONFIG_VGA_ARB) += vgaarb.o
# Endpoint library must be initialized before its users
obj-$(CONFIG_PCI_ENDPOINT) += endpoint/

View File

@ -1,32 +1,11 @@
// SPDX-License-Identifier: MIT
/*
* vgaarb.c: Implements the VGA arbitration. For details refer to
* Documentation/gpu/vgaarbiter.rst
*
*
* (C) Copyright 2005 Benjamin Herrenschmidt <benh@kernel.crashing.org>
* (C) Copyright 2007 Paulo R. Zanoni <przanoni@gmail.com>
* (C) Copyright 2007, 2009 Tiago Vignatti <vignatti@freedesktop.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS
* IN THE SOFTWARE.
*
*/
#define pr_fmt(fmt) "vgaarb: " fmt
@ -72,6 +51,7 @@ struct vga_device {
unsigned int io_norm_cnt; /* normal IO count */
unsigned int mem_norm_cnt; /* normal MEM count */
bool bridge_has_one_vga;
bool is_firmware_default; /* device selected by firmware */
unsigned int (*set_decode)(struct pci_dev *pdev, bool decode);
};
@ -122,8 +102,6 @@ both:
/* this is only used a cookie - it should not be dereferenced */
static struct pci_dev *vga_default;
static void vga_arb_device_card_gone(struct pci_dev *pdev);
/* Find somebody in our list */
static struct vga_device *vgadev_find(struct pci_dev *pdev)
{
@ -565,6 +543,144 @@ bail:
}
EXPORT_SYMBOL(vga_put);
static bool vga_is_firmware_default(struct pci_dev *pdev)
{
#if defined(CONFIG_X86) || defined(CONFIG_IA64)
u64 base = screen_info.lfb_base;
u64 size = screen_info.lfb_size;
u64 limit;
resource_size_t start, end;
unsigned long flags;
int i;
/* Select the device owning the boot framebuffer if there is one */
if (screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE)
base |= (u64)screen_info.ext_lfb_base << 32;
limit = base + size;
/* Does firmware framebuffer belong to us? */
for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
flags = pci_resource_flags(pdev, i);
if ((flags & IORESOURCE_MEM) == 0)
continue;
start = pci_resource_start(pdev, i);
end = pci_resource_end(pdev, i);
if (!start || !end)
continue;
if (base < start || limit >= end)
continue;
return true;
}
#endif
return false;
}
static bool vga_arb_integrated_gpu(struct device *dev)
{
#if defined(CONFIG_ACPI)
struct acpi_device *adev = ACPI_COMPANION(dev);
return adev && !strcmp(acpi_device_hid(adev), ACPI_VIDEO_HID);
#else
return false;
#endif
}
/*
* Return true if vgadev is a better default VGA device than the best one
* we've seen so far.
*/
static bool vga_is_boot_device(struct vga_device *vgadev)
{
struct vga_device *boot_vga = vgadev_find(vga_default_device());
struct pci_dev *pdev = vgadev->pdev;
u16 cmd, boot_cmd;
/*
* We select the default VGA device in this order:
* Firmware framebuffer (see vga_arb_select_default_device())
* Legacy VGA device (owns VGA_RSRC_LEGACY_MASK)
* Non-legacy integrated device (see vga_arb_select_default_device())
* Non-legacy discrete device (see vga_arb_select_default_device())
* Other device (see vga_arb_select_default_device())
*/
/*
* We always prefer a firmware default device, so if we've already
* found one, there's no need to consider vgadev.
*/
if (boot_vga && boot_vga->is_firmware_default)
return false;
if (vga_is_firmware_default(pdev)) {
vgadev->is_firmware_default = true;
return true;
}
/*
* A legacy VGA device has MEM and IO enabled and any bridges
* leading to it have PCI_BRIDGE_CTL_VGA enabled so the legacy
* resources ([mem 0xa0000-0xbffff], [io 0x3b0-0x3bb], etc) are
* routed to it.
*
* We use the first one we find, so if we've already found one,
* vgadev is no better.
*/
if (boot_vga &&
(boot_vga->owns & VGA_RSRC_LEGACY_MASK) == VGA_RSRC_LEGACY_MASK)
return false;
if ((vgadev->owns & VGA_RSRC_LEGACY_MASK) == VGA_RSRC_LEGACY_MASK)
return true;
/*
* If we haven't found a legacy VGA device, accept a non-legacy
* device. It may have either IO or MEM enabled, and bridges may
* not have PCI_BRIDGE_CTL_VGA enabled, so it may not be able to
* use legacy VGA resources. Prefer an integrated GPU over others.
*/
pci_read_config_word(pdev, PCI_COMMAND, &cmd);
if (cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) {
/*
* An integrated GPU overrides a previous non-legacy
* device. We expect only a single integrated GPU, but if
* there are more, we use the *last* because that was the
* previous behavior.
*/
if (vga_arb_integrated_gpu(&pdev->dev))
return true;
/*
* We prefer the first non-legacy discrete device we find.
* If we already found one, vgadev is no better.
*/
if (boot_vga) {
pci_read_config_word(boot_vga->pdev, PCI_COMMAND,
&boot_cmd);
if (boot_cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY))
return false;
}
return true;
}
/*
* vgadev has neither IO nor MEM enabled. If we haven't found any
* other VGA devices, it is the best candidate so far.
*/
if (!boot_vga)
return true;
return false;
}
/*
* Rules for using a bridge to control a VGA descendant decoding: if a bridge
* has only one VGA descendant then it can be used to control the VGA routing
@ -582,8 +698,10 @@ static void vga_arbiter_check_bridge_sharing(struct vga_device *vgadev)
vgadev->bridge_has_one_vga = true;
if (list_empty(&vga_list))
if (list_empty(&vga_list)) {
vgaarb_info(&vgadev->pdev->dev, "bridge control possible\n");
return;
}
/* okay iterate the new devices bridge hierarachy */
new_bus = vgadev->pdev->bus;
@ -622,6 +740,11 @@ static void vga_arbiter_check_bridge_sharing(struct vga_device *vgadev)
}
new_bus = new_bus->parent;
}
if (vgadev->bridge_has_one_vga)
vgaarb_info(&vgadev->pdev->dev, "bridge control possible\n");
else
vgaarb_info(&vgadev->pdev->dev, "no bridge control possible\n");
}
/*
@ -692,12 +815,10 @@ static bool vga_arbiter_add_pci_device(struct pci_dev *pdev)
bus = bus->parent;
}
/* Deal with VGA default device. Use first enabled one
* by default if arch doesn't have it's own hook
*/
if (vga_default == NULL &&
((vgadev->owns & VGA_RSRC_LEGACY_MASK) == VGA_RSRC_LEGACY_MASK)) {
vgaarb_info(&pdev->dev, "setting as boot VGA device\n");
if (vga_is_boot_device(vgadev)) {
vgaarb_info(&pdev->dev, "setting as boot VGA device%s\n",
vga_default_device() ?
" (overriding previous)" : "");
vga_set_default_device(pdev);
}
@ -741,10 +862,6 @@ static bool vga_arbiter_del_pci_device(struct pci_dev *pdev)
/* Remove entry from list */
list_del(&vgadev->list);
vga_count--;
/* Notify userland driver that the device is gone so it discards
* it's copies of the pci_dev pointer
*/
vga_arb_device_card_gone(pdev);
/* Wake up all possible waiters */
wake_up_all(&vga_wait_queue);
@ -994,9 +1111,7 @@ static ssize_t vga_arb_read(struct file *file, char __user *buf,
if (lbuf == NULL)
return -ENOMEM;
/* Shields against vga_arb_device_card_gone (pci_dev going
* away), and allows access to vga list
*/
/* Protects vga_list */
spin_lock_irqsave(&vga_lock, flags);
/* If we are targeting the default, use it */
@ -1013,8 +1128,6 @@ static ssize_t vga_arb_read(struct file *file, char __user *buf,
/* Wow, it's not in the list, that shouldn't happen,
* let's fix us up and return invalid card
*/
if (pdev == priv->target)
vga_arb_device_card_gone(pdev);
spin_unlock_irqrestore(&vga_lock, flags);
len = sprintf(lbuf, "invalid");
goto done;
@ -1022,7 +1135,7 @@ static ssize_t vga_arb_read(struct file *file, char __user *buf,
/* Fill the buffer with infos */
len = snprintf(lbuf, 1024,
"count:%d,PCI:%s,decodes=%s,owns=%s,locks=%s(%d:%d)\n",
"count:%d,PCI:%s,decodes=%s,owns=%s,locks=%s(%u:%u)\n",
vga_decode_count, pci_name(pdev),
vga_iostate_to_str(vgadev->decodes),
vga_iostate_to_str(vgadev->owns),
@ -1358,10 +1471,6 @@ static int vga_arb_release(struct inode *inode, struct file *file)
return 0;
}
static void vga_arb_device_card_gone(struct pci_dev *pdev)
{
}
/*
* callback any registered clients to let them know we have a
* change in VGA cards
@ -1430,111 +1539,10 @@ static struct miscdevice vga_arb_device = {
MISC_DYNAMIC_MINOR, "vga_arbiter", &vga_arb_device_fops
};
#if defined(CONFIG_ACPI)
static bool vga_arb_integrated_gpu(struct device *dev)
{
struct acpi_device *adev = ACPI_COMPANION(dev);
return adev && !strcmp(acpi_device_hid(adev), ACPI_VIDEO_HID);
}
#else
static bool vga_arb_integrated_gpu(struct device *dev)
{
return false;
}
#endif
static void __init vga_arb_select_default_device(void)
{
struct pci_dev *pdev, *found = NULL;
struct vga_device *vgadev;
#if defined(CONFIG_X86) || defined(CONFIG_IA64)
u64 base = screen_info.lfb_base;
u64 size = screen_info.lfb_size;
u64 limit;
resource_size_t start, end;
unsigned long flags;
int i;
if (screen_info.capabilities & VIDEO_CAPABILITY_64BIT_BASE)
base |= (u64)screen_info.ext_lfb_base << 32;
limit = base + size;
list_for_each_entry(vgadev, &vga_list, list) {
struct device *dev = &vgadev->pdev->dev;
/*
* Override vga_arbiter_add_pci_device()'s I/O based detection
* as it may take the wrong device (e.g. on Apple system under
* EFI).
*
* Select the device owning the boot framebuffer if there is
* one.
*/
/* Does firmware framebuffer belong to us? */
for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
flags = pci_resource_flags(vgadev->pdev, i);
if ((flags & IORESOURCE_MEM) == 0)
continue;
start = pci_resource_start(vgadev->pdev, i);
end = pci_resource_end(vgadev->pdev, i);
if (!start || !end)
continue;
if (base < start || limit >= end)
continue;
if (!vga_default_device())
vgaarb_info(dev, "setting as boot device\n");
else if (vgadev->pdev != vga_default_device())
vgaarb_info(dev, "overriding boot device\n");
vga_set_default_device(vgadev->pdev);
}
}
#endif
if (!vga_default_device()) {
list_for_each_entry_reverse(vgadev, &vga_list, list) {
struct device *dev = &vgadev->pdev->dev;
u16 cmd;
pdev = vgadev->pdev;
pci_read_config_word(pdev, PCI_COMMAND, &cmd);
if (cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) {
found = pdev;
if (vga_arb_integrated_gpu(dev))
break;
}
}
}
if (found) {
vgaarb_info(&found->dev, "setting as boot device (VGA legacy resources not available)\n");
vga_set_default_device(found);
return;
}
if (!vga_default_device()) {
vgadev = list_first_entry_or_null(&vga_list,
struct vga_device, list);
if (vgadev) {
struct device *dev = &vgadev->pdev->dev;
vgaarb_info(dev, "setting as boot device (VGA legacy resources not available)\n");
vga_set_default_device(vgadev->pdev);
}
}
}
static int __init vga_arb_device_init(void)
{
int rc;
struct pci_dev *pdev;
struct vga_device *vgadev;
rc = misc_register(&vga_arb_device);
if (rc < 0)
@ -1550,18 +1558,7 @@ static int __init vga_arb_device_init(void)
PCI_ANY_ID, pdev)) != NULL)
vga_arbiter_add_pci_device(pdev);
list_for_each_entry(vgadev, &vga_list, list) {
struct device *dev = &vgadev->pdev->dev;
if (vgadev->bridge_has_one_vga)
vgaarb_info(dev, "bridge control possible\n");
else
vgaarb_info(dev, "no bridge control possible\n");
}
vga_arb_select_default_device();
pr_info("loaded\n");
return rc;
}
subsys_initcall(vga_arb_device_init);
subsys_initcall_sync(vga_arb_device_init);