xref: /kernel/linux/linux-5.10/drivers/pci/remove.c (revision 8c2ecf20)
1// SPDX-License-Identifier: GPL-2.0
2#include <linux/pci.h>
3#include <linux/module.h>
4#include "pci.h"
5
6static void pci_free_resources(struct pci_dev *dev)
7{
8	int i;
9
10	for (i = 0; i < PCI_NUM_RESOURCES; i++) {
11		struct resource *res = dev->resource + i;
12		if (res->parent)
13			release_resource(res);
14	}
15}
16
17static void pci_stop_dev(struct pci_dev *dev)
18{
19	pci_pme_active(dev, false);
20
21	if (pci_dev_is_added(dev)) {
22		device_release_driver(&dev->dev);
23		pci_proc_detach_device(dev);
24		pci_remove_sysfs_dev_files(dev);
25
26		pci_dev_assign_added(dev, false);
27	}
28}
29
30static void pci_destroy_dev(struct pci_dev *dev)
31{
32	if (!dev->dev.kobj.parent)
33		return;
34
35	device_del(&dev->dev);
36
37	down_write(&pci_bus_sem);
38	list_del(&dev->bus_list);
39	up_write(&pci_bus_sem);
40
41	pcie_aspm_exit_link_state(dev);
42	pci_bridge_d3_update(dev);
43	pci_free_resources(dev);
44	put_device(&dev->dev);
45}
46
47void pci_remove_bus(struct pci_bus *bus)
48{
49	pci_proc_detach_bus(bus);
50
51	down_write(&pci_bus_sem);
52	list_del(&bus->node);
53	pci_bus_release_busn_res(bus);
54	up_write(&pci_bus_sem);
55	pci_remove_legacy_files(bus);
56
57	if (bus->ops->remove_bus)
58		bus->ops->remove_bus(bus);
59
60	pcibios_remove_bus(bus);
61	device_unregister(&bus->dev);
62}
63EXPORT_SYMBOL(pci_remove_bus);
64
65static void pci_stop_bus_device(struct pci_dev *dev)
66{
67	struct pci_bus *bus = dev->subordinate;
68	struct pci_dev *child, *tmp;
69
70	/*
71	 * Stopping an SR-IOV PF device removes all the associated VFs,
72	 * which will update the bus->devices list and confuse the
73	 * iterator.  Therefore, iterate in reverse so we remove the VFs
74	 * first, then the PF.
75	 */
76	if (bus) {
77		list_for_each_entry_safe_reverse(child, tmp,
78						 &bus->devices, bus_list)
79			pci_stop_bus_device(child);
80	}
81
82	pci_stop_dev(dev);
83}
84
85static void pci_remove_bus_device(struct pci_dev *dev)
86{
87	struct pci_bus *bus = dev->subordinate;
88	struct pci_dev *child, *tmp;
89
90	if (bus) {
91		list_for_each_entry_safe(child, tmp,
92					 &bus->devices, bus_list)
93			pci_remove_bus_device(child);
94
95		pci_remove_bus(bus);
96		dev->subordinate = NULL;
97	}
98
99	pci_destroy_dev(dev);
100}
101
102/**
103 * pci_stop_and_remove_bus_device - remove a PCI device and any children
104 * @dev: the device to remove
105 *
106 * Remove a PCI device from the device lists, informing the drivers
107 * that the device has been removed.  We also remove any subordinate
108 * buses and children in a depth-first manner.
109 *
110 * For each device we remove, delete the device structure from the
111 * device lists, remove the /proc entry, and notify userspace
112 * (/sbin/hotplug).
113 */
114void pci_stop_and_remove_bus_device(struct pci_dev *dev)
115{
116	pci_stop_bus_device(dev);
117	pci_remove_bus_device(dev);
118}
119EXPORT_SYMBOL(pci_stop_and_remove_bus_device);
120
121void pci_stop_and_remove_bus_device_locked(struct pci_dev *dev)
122{
123	pci_lock_rescan_remove();
124	pci_stop_and_remove_bus_device(dev);
125	pci_unlock_rescan_remove();
126}
127EXPORT_SYMBOL_GPL(pci_stop_and_remove_bus_device_locked);
128
129void pci_stop_root_bus(struct pci_bus *bus)
130{
131	struct pci_dev *child, *tmp;
132	struct pci_host_bridge *host_bridge;
133
134	if (!pci_is_root_bus(bus))
135		return;
136
137	host_bridge = to_pci_host_bridge(bus->bridge);
138	list_for_each_entry_safe_reverse(child, tmp,
139					 &bus->devices, bus_list)
140		pci_stop_bus_device(child);
141
142	/* stop the host bridge */
143	device_release_driver(&host_bridge->dev);
144}
145EXPORT_SYMBOL_GPL(pci_stop_root_bus);
146
147void pci_remove_root_bus(struct pci_bus *bus)
148{
149	struct pci_dev *child, *tmp;
150	struct pci_host_bridge *host_bridge;
151
152	if (!pci_is_root_bus(bus))
153		return;
154
155	host_bridge = to_pci_host_bridge(bus->bridge);
156	list_for_each_entry_safe(child, tmp,
157				 &bus->devices, bus_list)
158		pci_remove_bus_device(child);
159	pci_remove_bus(bus);
160	host_bridge->bus = NULL;
161
162	/* remove the host bridge */
163	device_del(&host_bridge->dev);
164}
165EXPORT_SYMBOL_GPL(pci_remove_root_bus);
166