diff options
-rw-r--r-- | Documentation/ABI/testing/sysfs-bus-pci | 11 | ||||
-rw-r--r-- | drivers/pci/access.c | 166 | ||||
-rw-r--r-- | drivers/pci/pci-sysfs.c | 109 | ||||
-rw-r--r-- | drivers/pci/pci.h | 19 | ||||
-rw-r--r-- | drivers/pci/probe.c | 3 | ||||
-rw-r--r-- | include/linux/pci.h | 3 |
6 files changed, 297 insertions, 14 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci new file mode 100644 index 000000000000..ceddcff4082a --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-pci | |||
@@ -0,0 +1,11 @@ | |||
1 | What: /sys/bus/pci/devices/.../vpd | ||
2 | Date: February 2008 | ||
3 | Contact: Ben Hutchings <bhutchings@solarflare.com> | ||
4 | Description: | ||
5 | A file named vpd in a device directory will be a | ||
6 | binary file containing the Vital Product Data for the | ||
7 | device. It should follow the VPD format defined in | ||
8 | PCI Specification 2.1 or 2.2, but users should consider | ||
9 | that some devices may have malformatted data. If the | ||
10 | underlying VPD has a writable section then the | ||
11 | corresponding section of this file will be writable. | ||
diff --git a/drivers/pci/access.c b/drivers/pci/access.c index fc405f0165d9..ec8f7002b09d 100644 --- a/drivers/pci/access.c +++ b/drivers/pci/access.c | |||
@@ -1,3 +1,4 @@ | |||
1 | #include <linux/delay.h> | ||
1 | #include <linux/pci.h> | 2 | #include <linux/pci.h> |
2 | #include <linux/module.h> | 3 | #include <linux/module.h> |
3 | #include <linux/sched.h> | 4 | #include <linux/sched.h> |
@@ -126,6 +127,171 @@ PCI_USER_WRITE_CONFIG(byte, u8) | |||
126 | PCI_USER_WRITE_CONFIG(word, u16) | 127 | PCI_USER_WRITE_CONFIG(word, u16) |
127 | PCI_USER_WRITE_CONFIG(dword, u32) | 128 | PCI_USER_WRITE_CONFIG(dword, u32) |
128 | 129 | ||
130 | /* VPD access through PCI 2.2+ VPD capability */ | ||
131 | |||
132 | #define PCI_VPD_PCI22_SIZE (PCI_VPD_ADDR_MASK + 1) | ||
133 | |||
134 | struct pci_vpd_pci22 { | ||
135 | struct pci_vpd base; | ||
136 | spinlock_t lock; /* controls access to hardware and the flags */ | ||
137 | u8 cap; | ||
138 | bool busy; | ||
139 | bool flag; /* value of F bit to wait for */ | ||
140 | }; | ||
141 | |||
142 | /* Wait for last operation to complete */ | ||
143 | static int pci_vpd_pci22_wait(struct pci_dev *dev) | ||
144 | { | ||
145 | struct pci_vpd_pci22 *vpd = | ||
146 | container_of(dev->vpd, struct pci_vpd_pci22, base); | ||
147 | u16 flag, status; | ||
148 | int wait; | ||
149 | int ret; | ||
150 | |||
151 | if (!vpd->busy) | ||
152 | return 0; | ||
153 | |||
154 | flag = vpd->flag ? PCI_VPD_ADDR_F : 0; | ||
155 | wait = vpd->flag ? 10 : 1000; /* read: 100 us; write: 10 ms */ | ||
156 | for (;;) { | ||
157 | ret = pci_user_read_config_word(dev, | ||
158 | vpd->cap + PCI_VPD_ADDR, | ||
159 | &status); | ||
160 | if (ret < 0) | ||
161 | return ret; | ||
162 | if ((status & PCI_VPD_ADDR_F) == flag) { | ||
163 | vpd->busy = false; | ||
164 | return 0; | ||
165 | } | ||
166 | if (wait-- == 0) | ||
167 | return -ETIMEDOUT; | ||
168 | udelay(10); | ||
169 | } | ||
170 | } | ||
171 | |||
172 | static int pci_vpd_pci22_read(struct pci_dev *dev, int pos, int size, | ||
173 | char *buf) | ||
174 | { | ||
175 | struct pci_vpd_pci22 *vpd = | ||
176 | container_of(dev->vpd, struct pci_vpd_pci22, base); | ||
177 | u32 val; | ||
178 | int ret; | ||
179 | int begin, end, i; | ||
180 | |||
181 | if (pos < 0 || pos > PCI_VPD_PCI22_SIZE || | ||
182 | size > PCI_VPD_PCI22_SIZE - pos) | ||
183 | return -EINVAL; | ||
184 | if (size == 0) | ||
185 | return 0; | ||
186 | |||
187 | spin_lock_irq(&vpd->lock); | ||
188 | ret = pci_vpd_pci22_wait(dev); | ||
189 | if (ret < 0) | ||
190 | goto out; | ||
191 | ret = pci_user_write_config_word(dev, vpd->cap + PCI_VPD_ADDR, | ||
192 | pos & ~3); | ||
193 | if (ret < 0) | ||
194 | goto out; | ||
195 | vpd->busy = true; | ||
196 | vpd->flag = 1; | ||
197 | ret = pci_vpd_pci22_wait(dev); | ||
198 | if (ret < 0) | ||
199 | goto out; | ||
200 | ret = pci_user_read_config_dword(dev, vpd->cap + PCI_VPD_DATA, | ||
201 | &val); | ||
202 | out: | ||
203 | spin_unlock_irq(&vpd->lock); | ||
204 | if (ret < 0) | ||
205 | return ret; | ||
206 | |||
207 | /* Convert to bytes */ | ||
208 | begin = pos & 3; | ||
209 | end = min(4, begin + size); | ||
210 | for (i = 0; i < end; ++i) { | ||
211 | if (i >= begin) | ||
212 | *buf++ = val; | ||
213 | val >>= 8; | ||
214 | } | ||
215 | return end - begin; | ||
216 | } | ||
217 | |||
218 | static int pci_vpd_pci22_write(struct pci_dev *dev, int pos, int size, | ||
219 | const char *buf) | ||
220 | { | ||
221 | struct pci_vpd_pci22 *vpd = | ||
222 | container_of(dev->vpd, struct pci_vpd_pci22, base); | ||
223 | u32 val; | ||
224 | int ret; | ||
225 | |||
226 | if (pos < 0 || pos > PCI_VPD_PCI22_SIZE || pos & 3 || | ||
227 | size > PCI_VPD_PCI22_SIZE - pos || size < 4) | ||
228 | return -EINVAL; | ||
229 | |||
230 | val = (u8) *buf++; | ||
231 | val |= ((u8) *buf++) << 8; | ||
232 | val |= ((u8) *buf++) << 16; | ||
233 | val |= ((u32)(u8) *buf++) << 24; | ||
234 | |||
235 | spin_lock_irq(&vpd->lock); | ||
236 | ret = pci_vpd_pci22_wait(dev); | ||
237 | if (ret < 0) | ||
238 | goto out; | ||
239 | ret = pci_user_write_config_dword(dev, vpd->cap + PCI_VPD_DATA, | ||
240 | val); | ||
241 | if (ret < 0) | ||
242 | goto out; | ||
243 | ret = pci_user_write_config_word(dev, vpd->cap + PCI_VPD_ADDR, | ||
244 | pos | PCI_VPD_ADDR_F); | ||
245 | if (ret < 0) | ||
246 | goto out; | ||
247 | vpd->busy = true; | ||
248 | vpd->flag = 0; | ||
249 | ret = pci_vpd_pci22_wait(dev); | ||
250 | out: | ||
251 | spin_unlock_irq(&vpd->lock); | ||
252 | if (ret < 0) | ||
253 | return ret; | ||
254 | |||
255 | return 4; | ||
256 | } | ||
257 | |||
258 | static int pci_vpd_pci22_get_size(struct pci_dev *dev) | ||
259 | { | ||
260 | return PCI_VPD_PCI22_SIZE; | ||
261 | } | ||
262 | |||
263 | static void pci_vpd_pci22_release(struct pci_dev *dev) | ||
264 | { | ||
265 | kfree(container_of(dev->vpd, struct pci_vpd_pci22, base)); | ||
266 | } | ||
267 | |||
268 | static struct pci_vpd_ops pci_vpd_pci22_ops = { | ||
269 | .read = pci_vpd_pci22_read, | ||
270 | .write = pci_vpd_pci22_write, | ||
271 | .get_size = pci_vpd_pci22_get_size, | ||
272 | .release = pci_vpd_pci22_release, | ||
273 | }; | ||
274 | |||
275 | int pci_vpd_pci22_init(struct pci_dev *dev) | ||
276 | { | ||
277 | struct pci_vpd_pci22 *vpd; | ||
278 | u8 cap; | ||
279 | |||
280 | cap = pci_find_capability(dev, PCI_CAP_ID_VPD); | ||
281 | if (!cap) | ||
282 | return -ENODEV; | ||
283 | vpd = kzalloc(sizeof(*vpd), GFP_ATOMIC); | ||
284 | if (!vpd) | ||
285 | return -ENOMEM; | ||
286 | |||
287 | vpd->base.ops = &pci_vpd_pci22_ops; | ||
288 | spin_lock_init(&vpd->lock); | ||
289 | vpd->cap = cap; | ||
290 | vpd->busy = false; | ||
291 | dev->vpd = &vpd->base; | ||
292 | return 0; | ||
293 | } | ||
294 | |||
129 | /** | 295 | /** |
130 | * pci_block_user_cfg_access - Block userspace PCI config reads/writes | 296 | * pci_block_user_cfg_access - Block userspace PCI config reads/writes |
131 | * @dev: pci device struct | 297 | * @dev: pci device struct |
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index f5b0b622c189..ae9a7695be97 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c | |||
@@ -343,6 +343,58 @@ pci_write_config(struct kobject *kobj, struct bin_attribute *bin_attr, | |||
343 | return count; | 343 | return count; |
344 | } | 344 | } |
345 | 345 | ||
346 | static ssize_t | ||
347 | pci_read_vpd(struct kobject *kobj, struct bin_attribute *bin_attr, | ||
348 | char *buf, loff_t off, size_t count) | ||
349 | { | ||
350 | struct pci_dev *dev = | ||
351 | to_pci_dev(container_of(kobj, struct device, kobj)); | ||
352 | int end; | ||
353 | int ret; | ||
354 | |||
355 | if (off > bin_attr->size) | ||
356 | count = 0; | ||
357 | else if (count > bin_attr->size - off) | ||
358 | count = bin_attr->size - off; | ||
359 | end = off + count; | ||
360 | |||
361 | while (off < end) { | ||
362 | ret = dev->vpd->ops->read(dev, off, end - off, buf); | ||
363 | if (ret < 0) | ||
364 | return ret; | ||
365 | buf += ret; | ||
366 | off += ret; | ||
367 | } | ||
368 | |||
369 | return count; | ||
370 | } | ||
371 | |||
372 | static ssize_t | ||
373 | pci_write_vpd(struct kobject *kobj, struct bin_attribute *bin_attr, | ||
374 | char *buf, loff_t off, size_t count) | ||
375 | { | ||
376 | struct pci_dev *dev = | ||
377 | to_pci_dev(container_of(kobj, struct device, kobj)); | ||
378 | int end; | ||
379 | int ret; | ||
380 | |||
381 | if (off > bin_attr->size) | ||
382 | count = 0; | ||
383 | else if (count > bin_attr->size - off) | ||
384 | count = bin_attr->size - off; | ||
385 | end = off + count; | ||
386 | |||
387 | while (off < end) { | ||
388 | ret = dev->vpd->ops->write(dev, off, end - off, buf); | ||
389 | if (ret < 0) | ||
390 | return ret; | ||
391 | buf += ret; | ||
392 | off += ret; | ||
393 | } | ||
394 | |||
395 | return count; | ||
396 | } | ||
397 | |||
346 | #ifdef HAVE_PCI_LEGACY | 398 | #ifdef HAVE_PCI_LEGACY |
347 | /** | 399 | /** |
348 | * pci_read_legacy_io - read byte(s) from legacy I/O port space | 400 | * pci_read_legacy_io - read byte(s) from legacy I/O port space |
@@ -611,7 +663,7 @@ int __attribute__ ((weak)) pcibios_add_platform_entries(struct pci_dev *dev) | |||
611 | 663 | ||
612 | int __must_check pci_create_sysfs_dev_files (struct pci_dev *pdev) | 664 | int __must_check pci_create_sysfs_dev_files (struct pci_dev *pdev) |
613 | { | 665 | { |
614 | struct bin_attribute *rom_attr = NULL; | 666 | struct bin_attribute *attr = NULL; |
615 | int retval; | 667 | int retval; |
616 | 668 | ||
617 | if (!sysfs_initialized) | 669 | if (!sysfs_initialized) |
@@ -624,22 +676,41 @@ int __must_check pci_create_sysfs_dev_files (struct pci_dev *pdev) | |||
624 | if (retval) | 676 | if (retval) |
625 | goto err; | 677 | goto err; |
626 | 678 | ||
679 | /* If the device has VPD, try to expose it in sysfs. */ | ||
680 | if (pdev->vpd) { | ||
681 | attr = kzalloc(sizeof(*attr), GFP_ATOMIC); | ||
682 | if (attr) { | ||
683 | pdev->vpd->attr = attr; | ||
684 | attr->size = pdev->vpd->ops->get_size(pdev); | ||
685 | attr->attr.name = "vpd"; | ||
686 | attr->attr.mode = S_IRUGO | S_IWUSR; | ||
687 | attr->read = pci_read_vpd; | ||
688 | attr->write = pci_write_vpd; | ||
689 | retval = sysfs_create_bin_file(&pdev->dev.kobj, attr); | ||
690 | if (retval) | ||
691 | goto err_vpd; | ||
692 | } else { | ||
693 | retval = -ENOMEM; | ||
694 | goto err_config_file; | ||
695 | } | ||
696 | } | ||
697 | |||
627 | retval = pci_create_resource_files(pdev); | 698 | retval = pci_create_resource_files(pdev); |
628 | if (retval) | 699 | if (retval) |
629 | goto err_bin_file; | 700 | goto err_vpd_file; |
630 | 701 | ||
631 | /* If the device has a ROM, try to expose it in sysfs. */ | 702 | /* If the device has a ROM, try to expose it in sysfs. */ |
632 | if (pci_resource_len(pdev, PCI_ROM_RESOURCE) || | 703 | if (pci_resource_len(pdev, PCI_ROM_RESOURCE) || |
633 | (pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW)) { | 704 | (pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW)) { |
634 | rom_attr = kzalloc(sizeof(*rom_attr), GFP_ATOMIC); | 705 | attr = kzalloc(sizeof(*attr), GFP_ATOMIC); |
635 | if (rom_attr) { | 706 | if (attr) { |
636 | pdev->rom_attr = rom_attr; | 707 | pdev->rom_attr = attr; |
637 | rom_attr->size = pci_resource_len(pdev, PCI_ROM_RESOURCE); | 708 | attr->size = pci_resource_len(pdev, PCI_ROM_RESOURCE); |
638 | rom_attr->attr.name = "rom"; | 709 | attr->attr.name = "rom"; |
639 | rom_attr->attr.mode = S_IRUSR; | 710 | attr->attr.mode = S_IRUSR; |
640 | rom_attr->read = pci_read_rom; | 711 | attr->read = pci_read_rom; |
641 | rom_attr->write = pci_write_rom; | 712 | attr->write = pci_write_rom; |
642 | retval = sysfs_create_bin_file(&pdev->dev.kobj, rom_attr); | 713 | retval = sysfs_create_bin_file(&pdev->dev.kobj, attr); |
643 | if (retval) | 714 | if (retval) |
644 | goto err_rom; | 715 | goto err_rom; |
645 | } else { | 716 | } else { |
@@ -657,12 +728,18 @@ int __must_check pci_create_sysfs_dev_files (struct pci_dev *pdev) | |||
657 | 728 | ||
658 | err_rom_file: | 729 | err_rom_file: |
659 | if (pci_resource_len(pdev, PCI_ROM_RESOURCE)) | 730 | if (pci_resource_len(pdev, PCI_ROM_RESOURCE)) |
660 | sysfs_remove_bin_file(&pdev->dev.kobj, rom_attr); | 731 | sysfs_remove_bin_file(&pdev->dev.kobj, pdev->rom_attr); |
661 | err_rom: | 732 | err_rom: |
662 | kfree(rom_attr); | 733 | kfree(pdev->rom_attr); |
663 | err_resource_files: | 734 | err_resource_files: |
664 | pci_remove_resource_files(pdev); | 735 | pci_remove_resource_files(pdev); |
665 | err_bin_file: | 736 | err_vpd_file: |
737 | if (pdev->vpd) { | ||
738 | sysfs_remove_bin_file(&pdev->dev.kobj, pdev->vpd->attr); | ||
739 | err_vpd: | ||
740 | kfree(pdev->vpd->attr); | ||
741 | } | ||
742 | err_config_file: | ||
666 | if (pdev->cfg_size < 4096) | 743 | if (pdev->cfg_size < 4096) |
667 | sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr); | 744 | sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr); |
668 | else | 745 | else |
@@ -684,6 +761,10 @@ void pci_remove_sysfs_dev_files(struct pci_dev *pdev) | |||
684 | 761 | ||
685 | pcie_aspm_remove_sysfs_dev_files(pdev); | 762 | pcie_aspm_remove_sysfs_dev_files(pdev); |
686 | 763 | ||
764 | if (pdev->vpd) { | ||
765 | sysfs_remove_bin_file(&pdev->dev.kobj, pdev->vpd->attr); | ||
766 | kfree(pdev->vpd->attr); | ||
767 | } | ||
687 | if (pdev->cfg_size < 4096) | 768 | if (pdev->cfg_size < 4096) |
688 | sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr); | 769 | sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr); |
689 | else | 770 | else |
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index eabeb1f2ec99..0a497c1b4227 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h | |||
@@ -18,6 +18,25 @@ extern int pci_user_write_config_byte(struct pci_dev *dev, int where, u8 val); | |||
18 | extern int pci_user_write_config_word(struct pci_dev *dev, int where, u16 val); | 18 | extern int pci_user_write_config_word(struct pci_dev *dev, int where, u16 val); |
19 | extern int pci_user_write_config_dword(struct pci_dev *dev, int where, u32 val); | 19 | extern int pci_user_write_config_dword(struct pci_dev *dev, int where, u32 val); |
20 | 20 | ||
21 | struct pci_vpd_ops { | ||
22 | int (*read)(struct pci_dev *dev, int pos, int size, char *buf); | ||
23 | int (*write)(struct pci_dev *dev, int pos, int size, const char *buf); | ||
24 | int (*get_size)(struct pci_dev *dev); | ||
25 | void (*release)(struct pci_dev *dev); | ||
26 | }; | ||
27 | |||
28 | struct pci_vpd { | ||
29 | struct pci_vpd_ops *ops; | ||
30 | struct bin_attribute *attr; /* descriptor for sysfs VPD entry */ | ||
31 | }; | ||
32 | |||
33 | extern int pci_vpd_pci22_init(struct pci_dev *dev); | ||
34 | static inline void pci_vpd_release(struct pci_dev *dev) | ||
35 | { | ||
36 | if (dev->vpd) | ||
37 | dev->vpd->ops->release(dev); | ||
38 | } | ||
39 | |||
21 | /* PCI /proc functions */ | 40 | /* PCI /proc functions */ |
22 | #ifdef CONFIG_PROC_FS | 41 | #ifdef CONFIG_PROC_FS |
23 | extern int pci_proc_attach_device(struct pci_dev *dev); | 42 | extern int pci_proc_attach_device(struct pci_dev *dev); |
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 284ef392c3ea..c2e99fd87faf 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c | |||
@@ -794,6 +794,7 @@ static void pci_release_dev(struct device *dev) | |||
794 | struct pci_dev *pci_dev; | 794 | struct pci_dev *pci_dev; |
795 | 795 | ||
796 | pci_dev = to_pci_dev(dev); | 796 | pci_dev = to_pci_dev(dev); |
797 | pci_vpd_release(pci_dev); | ||
797 | kfree(pci_dev); | 798 | kfree(pci_dev); |
798 | } | 799 | } |
799 | 800 | ||
@@ -933,6 +934,8 @@ pci_scan_device(struct pci_bus *bus, int devfn) | |||
933 | return NULL; | 934 | return NULL; |
934 | } | 935 | } |
935 | 936 | ||
937 | pci_vpd_pci22_init(dev); | ||
938 | |||
936 | return dev; | 939 | return dev; |
937 | } | 940 | } |
938 | 941 | ||
diff --git a/include/linux/pci.h b/include/linux/pci.h index e2f46b05cf8b..292491324b01 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h | |||
@@ -20,6 +20,8 @@ | |||
20 | /* Include the pci register defines */ | 20 | /* Include the pci register defines */ |
21 | #include <linux/pci_regs.h> | 21 | #include <linux/pci_regs.h> |
22 | 22 | ||
23 | struct pci_vpd; | ||
24 | |||
23 | /* | 25 | /* |
24 | * The PCI interface treats multi-function devices as independent | 26 | * The PCI interface treats multi-function devices as independent |
25 | * devices. The slot/function address of each device is encoded | 27 | * devices. The slot/function address of each device is encoded |
@@ -206,6 +208,7 @@ struct pci_dev { | |||
206 | #ifdef CONFIG_PCI_MSI | 208 | #ifdef CONFIG_PCI_MSI |
207 | struct list_head msi_list; | 209 | struct list_head msi_list; |
208 | #endif | 210 | #endif |
211 | struct pci_vpd *vpd; | ||
209 | }; | 212 | }; |
210 | 213 | ||
211 | extern struct pci_dev *alloc_pci_dev(void); | 214 | extern struct pci_dev *alloc_pci_dev(void); |