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