diff options
author | Yu Zhao <yu.zhao@intel.com> | 2009-03-19 23:25:15 -0400 |
---|---|---|
committer | Jesse Barnes <jbarnes@virtuousgeek.org> | 2009-03-20 13:48:26 -0400 |
commit | dd7cc44d0bcec5e9c42fe52e88dc254ae62eac8d (patch) | |
tree | 742b2c903580eded1e352988b068c0362eccc634 | |
parent | 480b93b7837fb3cf0579a42f4953ac463a5b9e1e (diff) |
PCI: add SR-IOV API for Physical Function driver
Add or remove the Virtual Function when the SR-IOV is enabled or
disabled by the device driver. This can happen anytime rather than
only at the device probe stage.
Reviewed-by: Matthew Wilcox <willy@linux.intel.com>
Signed-off-by: Yu Zhao <yu.zhao@intel.com>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
-rw-r--r-- | drivers/pci/iov.c | 314 | ||||
-rw-r--r-- | drivers/pci/pci.h | 2 | ||||
-rw-r--r-- | include/linux/pci.h | 19 |
3 files changed, 334 insertions, 1 deletions
diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c index 5ddfc09a8d3f..d0ff8ad8f7ba 100644 --- a/drivers/pci/iov.c +++ b/drivers/pci/iov.c | |||
@@ -13,6 +13,7 @@ | |||
13 | #include <linux/delay.h> | 13 | #include <linux/delay.h> |
14 | #include "pci.h" | 14 | #include "pci.h" |
15 | 15 | ||
16 | #define VIRTFN_ID_LEN 16 | ||
16 | 17 | ||
17 | static inline u8 virtfn_bus(struct pci_dev *dev, int id) | 18 | static inline u8 virtfn_bus(struct pci_dev *dev, int id) |
18 | { | 19 | { |
@@ -26,6 +27,284 @@ static inline u8 virtfn_devfn(struct pci_dev *dev, int id) | |||
26 | dev->sriov->stride * id) & 0xff; | 27 | dev->sriov->stride * id) & 0xff; |
27 | } | 28 | } |
28 | 29 | ||
30 | static struct pci_bus *virtfn_add_bus(struct pci_bus *bus, int busnr) | ||
31 | { | ||
32 | int rc; | ||
33 | struct pci_bus *child; | ||
34 | |||
35 | if (bus->number == busnr) | ||
36 | return bus; | ||
37 | |||
38 | child = pci_find_bus(pci_domain_nr(bus), busnr); | ||
39 | if (child) | ||
40 | return child; | ||
41 | |||
42 | child = pci_add_new_bus(bus, NULL, busnr); | ||
43 | if (!child) | ||
44 | return NULL; | ||
45 | |||
46 | child->subordinate = busnr; | ||
47 | child->dev.parent = bus->bridge; | ||
48 | rc = pci_bus_add_child(child); | ||
49 | if (rc) { | ||
50 | pci_remove_bus(child); | ||
51 | return NULL; | ||
52 | } | ||
53 | |||
54 | return child; | ||
55 | } | ||
56 | |||
57 | static void virtfn_remove_bus(struct pci_bus *bus, int busnr) | ||
58 | { | ||
59 | struct pci_bus *child; | ||
60 | |||
61 | if (bus->number == busnr) | ||
62 | return; | ||
63 | |||
64 | child = pci_find_bus(pci_domain_nr(bus), busnr); | ||
65 | BUG_ON(!child); | ||
66 | |||
67 | if (list_empty(&child->devices)) | ||
68 | pci_remove_bus(child); | ||
69 | } | ||
70 | |||
71 | static int virtfn_add(struct pci_dev *dev, int id, int reset) | ||
72 | { | ||
73 | int i; | ||
74 | int rc; | ||
75 | u64 size; | ||
76 | char buf[VIRTFN_ID_LEN]; | ||
77 | struct pci_dev *virtfn; | ||
78 | struct resource *res; | ||
79 | struct pci_sriov *iov = dev->sriov; | ||
80 | |||
81 | virtfn = alloc_pci_dev(); | ||
82 | if (!virtfn) | ||
83 | return -ENOMEM; | ||
84 | |||
85 | mutex_lock(&iov->dev->sriov->lock); | ||
86 | virtfn->bus = virtfn_add_bus(dev->bus, virtfn_bus(dev, id)); | ||
87 | if (!virtfn->bus) { | ||
88 | kfree(virtfn); | ||
89 | mutex_unlock(&iov->dev->sriov->lock); | ||
90 | return -ENOMEM; | ||
91 | } | ||
92 | virtfn->devfn = virtfn_devfn(dev, id); | ||
93 | virtfn->vendor = dev->vendor; | ||
94 | pci_read_config_word(dev, iov->pos + PCI_SRIOV_VF_DID, &virtfn->device); | ||
95 | pci_setup_device(virtfn); | ||
96 | virtfn->dev.parent = dev->dev.parent; | ||
97 | |||
98 | for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) { | ||
99 | res = dev->resource + PCI_IOV_RESOURCES + i; | ||
100 | if (!res->parent) | ||
101 | continue; | ||
102 | virtfn->resource[i].name = pci_name(virtfn); | ||
103 | virtfn->resource[i].flags = res->flags; | ||
104 | size = resource_size(res); | ||
105 | do_div(size, iov->total); | ||
106 | virtfn->resource[i].start = res->start + size * id; | ||
107 | virtfn->resource[i].end = virtfn->resource[i].start + size - 1; | ||
108 | rc = request_resource(res, &virtfn->resource[i]); | ||
109 | BUG_ON(rc); | ||
110 | } | ||
111 | |||
112 | if (reset) | ||
113 | pci_execute_reset_function(virtfn); | ||
114 | |||
115 | pci_device_add(virtfn, virtfn->bus); | ||
116 | mutex_unlock(&iov->dev->sriov->lock); | ||
117 | |||
118 | virtfn->physfn = pci_dev_get(dev); | ||
119 | virtfn->is_virtfn = 1; | ||
120 | |||
121 | rc = pci_bus_add_device(virtfn); | ||
122 | if (rc) | ||
123 | goto failed1; | ||
124 | sprintf(buf, "virtfn%u", id); | ||
125 | rc = sysfs_create_link(&dev->dev.kobj, &virtfn->dev.kobj, buf); | ||
126 | if (rc) | ||
127 | goto failed1; | ||
128 | rc = sysfs_create_link(&virtfn->dev.kobj, &dev->dev.kobj, "physfn"); | ||
129 | if (rc) | ||
130 | goto failed2; | ||
131 | |||
132 | kobject_uevent(&virtfn->dev.kobj, KOBJ_CHANGE); | ||
133 | |||
134 | return 0; | ||
135 | |||
136 | failed2: | ||
137 | sysfs_remove_link(&dev->dev.kobj, buf); | ||
138 | failed1: | ||
139 | pci_dev_put(dev); | ||
140 | mutex_lock(&iov->dev->sriov->lock); | ||
141 | pci_remove_bus_device(virtfn); | ||
142 | virtfn_remove_bus(dev->bus, virtfn_bus(dev, id)); | ||
143 | mutex_unlock(&iov->dev->sriov->lock); | ||
144 | |||
145 | return rc; | ||
146 | } | ||
147 | |||
148 | static void virtfn_remove(struct pci_dev *dev, int id, int reset) | ||
149 | { | ||
150 | char buf[VIRTFN_ID_LEN]; | ||
151 | struct pci_bus *bus; | ||
152 | struct pci_dev *virtfn; | ||
153 | struct pci_sriov *iov = dev->sriov; | ||
154 | |||
155 | bus = pci_find_bus(pci_domain_nr(dev->bus), virtfn_bus(dev, id)); | ||
156 | if (!bus) | ||
157 | return; | ||
158 | |||
159 | virtfn = pci_get_slot(bus, virtfn_devfn(dev, id)); | ||
160 | if (!virtfn) | ||
161 | return; | ||
162 | |||
163 | pci_dev_put(virtfn); | ||
164 | |||
165 | if (reset) { | ||
166 | device_release_driver(&virtfn->dev); | ||
167 | pci_execute_reset_function(virtfn); | ||
168 | } | ||
169 | |||
170 | sprintf(buf, "virtfn%u", id); | ||
171 | sysfs_remove_link(&dev->dev.kobj, buf); | ||
172 | sysfs_remove_link(&virtfn->dev.kobj, "physfn"); | ||
173 | |||
174 | mutex_lock(&iov->dev->sriov->lock); | ||
175 | pci_remove_bus_device(virtfn); | ||
176 | virtfn_remove_bus(dev->bus, virtfn_bus(dev, id)); | ||
177 | mutex_unlock(&iov->dev->sriov->lock); | ||
178 | |||
179 | pci_dev_put(dev); | ||
180 | } | ||
181 | |||
182 | static int sriov_enable(struct pci_dev *dev, int nr_virtfn) | ||
183 | { | ||
184 | int rc; | ||
185 | int i, j; | ||
186 | int nres; | ||
187 | u16 offset, stride, initial; | ||
188 | struct resource *res; | ||
189 | struct pci_dev *pdev; | ||
190 | struct pci_sriov *iov = dev->sriov; | ||
191 | |||
192 | if (!nr_virtfn) | ||
193 | return 0; | ||
194 | |||
195 | if (iov->nr_virtfn) | ||
196 | return -EINVAL; | ||
197 | |||
198 | pci_read_config_word(dev, iov->pos + PCI_SRIOV_INITIAL_VF, &initial); | ||
199 | if (initial > iov->total || | ||
200 | (!(iov->cap & PCI_SRIOV_CAP_VFM) && (initial != iov->total))) | ||
201 | return -EIO; | ||
202 | |||
203 | if (nr_virtfn < 0 || nr_virtfn > iov->total || | ||
204 | (!(iov->cap & PCI_SRIOV_CAP_VFM) && (nr_virtfn > initial))) | ||
205 | return -EINVAL; | ||
206 | |||
207 | pci_write_config_word(dev, iov->pos + PCI_SRIOV_NUM_VF, nr_virtfn); | ||
208 | pci_read_config_word(dev, iov->pos + PCI_SRIOV_VF_OFFSET, &offset); | ||
209 | pci_read_config_word(dev, iov->pos + PCI_SRIOV_VF_STRIDE, &stride); | ||
210 | if (!offset || (nr_virtfn > 1 && !stride)) | ||
211 | return -EIO; | ||
212 | |||
213 | nres = 0; | ||
214 | for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) { | ||
215 | res = dev->resource + PCI_IOV_RESOURCES + i; | ||
216 | if (res->parent) | ||
217 | nres++; | ||
218 | } | ||
219 | if (nres != iov->nres) { | ||
220 | dev_err(&dev->dev, "not enough MMIO resources for SR-IOV\n"); | ||
221 | return -ENOMEM; | ||
222 | } | ||
223 | |||
224 | iov->offset = offset; | ||
225 | iov->stride = stride; | ||
226 | |||
227 | if (virtfn_bus(dev, nr_virtfn - 1) > dev->bus->subordinate) { | ||
228 | dev_err(&dev->dev, "SR-IOV: bus number out of range\n"); | ||
229 | return -ENOMEM; | ||
230 | } | ||
231 | |||
232 | if (iov->link != dev->devfn) { | ||
233 | pdev = pci_get_slot(dev->bus, iov->link); | ||
234 | if (!pdev) | ||
235 | return -ENODEV; | ||
236 | |||
237 | pci_dev_put(pdev); | ||
238 | |||
239 | if (!pdev->is_physfn) | ||
240 | return -ENODEV; | ||
241 | |||
242 | rc = sysfs_create_link(&dev->dev.kobj, | ||
243 | &pdev->dev.kobj, "dep_link"); | ||
244 | if (rc) | ||
245 | return rc; | ||
246 | } | ||
247 | |||
248 | iov->ctrl |= PCI_SRIOV_CTRL_VFE | PCI_SRIOV_CTRL_MSE; | ||
249 | pci_block_user_cfg_access(dev); | ||
250 | pci_write_config_word(dev, iov->pos + PCI_SRIOV_CTRL, iov->ctrl); | ||
251 | msleep(100); | ||
252 | pci_unblock_user_cfg_access(dev); | ||
253 | |||
254 | iov->initial = initial; | ||
255 | if (nr_virtfn < initial) | ||
256 | initial = nr_virtfn; | ||
257 | |||
258 | for (i = 0; i < initial; i++) { | ||
259 | rc = virtfn_add(dev, i, 0); | ||
260 | if (rc) | ||
261 | goto failed; | ||
262 | } | ||
263 | |||
264 | kobject_uevent(&dev->dev.kobj, KOBJ_CHANGE); | ||
265 | iov->nr_virtfn = nr_virtfn; | ||
266 | |||
267 | return 0; | ||
268 | |||
269 | failed: | ||
270 | for (j = 0; j < i; j++) | ||
271 | virtfn_remove(dev, j, 0); | ||
272 | |||
273 | iov->ctrl &= ~(PCI_SRIOV_CTRL_VFE | PCI_SRIOV_CTRL_MSE); | ||
274 | pci_block_user_cfg_access(dev); | ||
275 | pci_write_config_word(dev, iov->pos + PCI_SRIOV_CTRL, iov->ctrl); | ||
276 | ssleep(1); | ||
277 | pci_unblock_user_cfg_access(dev); | ||
278 | |||
279 | if (iov->link != dev->devfn) | ||
280 | sysfs_remove_link(&dev->dev.kobj, "dep_link"); | ||
281 | |||
282 | return rc; | ||
283 | } | ||
284 | |||
285 | static void sriov_disable(struct pci_dev *dev) | ||
286 | { | ||
287 | int i; | ||
288 | struct pci_sriov *iov = dev->sriov; | ||
289 | |||
290 | if (!iov->nr_virtfn) | ||
291 | return; | ||
292 | |||
293 | for (i = 0; i < iov->nr_virtfn; i++) | ||
294 | virtfn_remove(dev, i, 0); | ||
295 | |||
296 | iov->ctrl &= ~(PCI_SRIOV_CTRL_VFE | PCI_SRIOV_CTRL_MSE); | ||
297 | pci_block_user_cfg_access(dev); | ||
298 | pci_write_config_word(dev, iov->pos + PCI_SRIOV_CTRL, iov->ctrl); | ||
299 | ssleep(1); | ||
300 | pci_unblock_user_cfg_access(dev); | ||
301 | |||
302 | if (iov->link != dev->devfn) | ||
303 | sysfs_remove_link(&dev->dev.kobj, "dep_link"); | ||
304 | |||
305 | iov->nr_virtfn = 0; | ||
306 | } | ||
307 | |||
29 | static int sriov_init(struct pci_dev *dev, int pos) | 308 | static int sriov_init(struct pci_dev *dev, int pos) |
30 | { | 309 | { |
31 | int i; | 310 | int i; |
@@ -132,6 +411,8 @@ failed: | |||
132 | 411 | ||
133 | static void sriov_release(struct pci_dev *dev) | 412 | static void sriov_release(struct pci_dev *dev) |
134 | { | 413 | { |
414 | BUG_ON(dev->sriov->nr_virtfn); | ||
415 | |||
135 | if (dev == dev->sriov->dev) | 416 | if (dev == dev->sriov->dev) |
136 | mutex_destroy(&dev->sriov->lock); | 417 | mutex_destroy(&dev->sriov->lock); |
137 | else | 418 | else |
@@ -155,6 +436,7 @@ static void sriov_restore_state(struct pci_dev *dev) | |||
155 | pci_update_resource(dev, i); | 436 | pci_update_resource(dev, i); |
156 | 437 | ||
157 | pci_write_config_dword(dev, iov->pos + PCI_SRIOV_SYS_PGSIZE, iov->pgsz); | 438 | pci_write_config_dword(dev, iov->pos + PCI_SRIOV_SYS_PGSIZE, iov->pgsz); |
439 | pci_write_config_word(dev, iov->pos + PCI_SRIOV_NUM_VF, iov->nr_virtfn); | ||
158 | pci_write_config_word(dev, iov->pos + PCI_SRIOV_CTRL, iov->ctrl); | 440 | pci_write_config_word(dev, iov->pos + PCI_SRIOV_CTRL, iov->ctrl); |
159 | if (iov->ctrl & PCI_SRIOV_CTRL_VFE) | 441 | if (iov->ctrl & PCI_SRIOV_CTRL_VFE) |
160 | msleep(100); | 442 | msleep(100); |
@@ -245,3 +527,35 @@ int pci_iov_bus_range(struct pci_bus *bus) | |||
245 | 527 | ||
246 | return max ? max - bus->number : 0; | 528 | return max ? max - bus->number : 0; |
247 | } | 529 | } |
530 | |||
531 | /** | ||
532 | * pci_enable_sriov - enable the SR-IOV capability | ||
533 | * @dev: the PCI device | ||
534 | * | ||
535 | * Returns 0 on success, or negative on failure. | ||
536 | */ | ||
537 | int pci_enable_sriov(struct pci_dev *dev, int nr_virtfn) | ||
538 | { | ||
539 | might_sleep(); | ||
540 | |||
541 | if (!dev->is_physfn) | ||
542 | return -ENODEV; | ||
543 | |||
544 | return sriov_enable(dev, nr_virtfn); | ||
545 | } | ||
546 | EXPORT_SYMBOL_GPL(pci_enable_sriov); | ||
547 | |||
548 | /** | ||
549 | * pci_disable_sriov - disable the SR-IOV capability | ||
550 | * @dev: the PCI device | ||
551 | */ | ||
552 | void pci_disable_sriov(struct pci_dev *dev) | ||
553 | { | ||
554 | might_sleep(); | ||
555 | |||
556 | if (!dev->is_physfn) | ||
557 | return; | ||
558 | |||
559 | sriov_disable(dev); | ||
560 | } | ||
561 | EXPORT_SYMBOL_GPL(pci_disable_sriov); | ||
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index f4fc10fc5872..0f1c7d103509 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h | |||
@@ -209,6 +209,8 @@ struct pci_sriov { | |||
209 | u32 cap; /* SR-IOV Capabilities */ | 209 | u32 cap; /* SR-IOV Capabilities */ |
210 | u16 ctrl; /* SR-IOV Control */ | 210 | u16 ctrl; /* SR-IOV Control */ |
211 | u16 total; /* total VFs associated with the PF */ | 211 | u16 total; /* total VFs associated with the PF */ |
212 | u16 initial; /* initial VFs associated with the PF */ | ||
213 | u16 nr_virtfn; /* number of VFs available */ | ||
212 | u16 offset; /* first VF Routing ID offset */ | 214 | u16 offset; /* first VF Routing ID offset */ |
213 | u16 stride; /* following VF stride */ | 215 | u16 stride; /* following VF stride */ |
214 | u32 pgsz; /* page size for BAR alignment */ | 216 | u32 pgsz; /* page size for BAR alignment */ |
diff --git a/include/linux/pci.h b/include/linux/pci.h index 8ce2f2d9ab63..c2e491e04063 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h | |||
@@ -265,6 +265,7 @@ struct pci_dev { | |||
265 | unsigned int is_pcie:1; | 265 | unsigned int is_pcie:1; |
266 | unsigned int state_saved:1; | 266 | unsigned int state_saved:1; |
267 | unsigned int is_physfn:1; | 267 | unsigned int is_physfn:1; |
268 | unsigned int is_virtfn:1; | ||
268 | pci_dev_flags_t dev_flags; | 269 | pci_dev_flags_t dev_flags; |
269 | atomic_t enable_cnt; /* pci_enable_device has been called */ | 270 | atomic_t enable_cnt; /* pci_enable_device has been called */ |
270 | 271 | ||
@@ -279,7 +280,10 @@ struct pci_dev { | |||
279 | #endif | 280 | #endif |
280 | struct pci_vpd *vpd; | 281 | struct pci_vpd *vpd; |
281 | #ifdef CONFIG_PCI_IOV | 282 | #ifdef CONFIG_PCI_IOV |
282 | struct pci_sriov *sriov; /* SR-IOV capability related */ | 283 | union { |
284 | struct pci_sriov *sriov; /* SR-IOV capability related */ | ||
285 | struct pci_dev *physfn; /* the PF this VF is associated with */ | ||
286 | }; | ||
283 | #endif | 287 | #endif |
284 | }; | 288 | }; |
285 | 289 | ||
@@ -1212,5 +1216,18 @@ int pci_ext_cfg_avail(struct pci_dev *dev); | |||
1212 | 1216 | ||
1213 | void __iomem *pci_ioremap_bar(struct pci_dev *pdev, int bar); | 1217 | void __iomem *pci_ioremap_bar(struct pci_dev *pdev, int bar); |
1214 | 1218 | ||
1219 | #ifdef CONFIG_PCI_IOV | ||
1220 | extern int pci_enable_sriov(struct pci_dev *dev, int nr_virtfn); | ||
1221 | extern void pci_disable_sriov(struct pci_dev *dev); | ||
1222 | #else | ||
1223 | static inline int pci_enable_sriov(struct pci_dev *dev, int nr_virtfn) | ||
1224 | { | ||
1225 | return -ENODEV; | ||
1226 | } | ||
1227 | static inline void pci_disable_sriov(struct pci_dev *dev) | ||
1228 | { | ||
1229 | } | ||
1230 | #endif | ||
1231 | |||
1215 | #endif /* __KERNEL__ */ | 1232 | #endif /* __KERNEL__ */ |
1216 | #endif /* LINUX_PCI_H */ | 1233 | #endif /* LINUX_PCI_H */ |