1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Sysfs entries for PCI Error Recovery for PAPR-compliant platform. |
4 | * Copyright IBM Corporation 2007 |
5 | * Copyright Linas Vepstas <linas@austin.ibm.com> 2007 |
6 | * |
7 | * Send comments and feedback to Linas Vepstas <linas@austin.ibm.com> |
8 | */ |
9 | #include <linux/of.h> |
10 | #include <linux/pci.h> |
11 | #include <linux/stat.h> |
12 | #include <asm/ppc-pci.h> |
13 | #include <asm/pci-bridge.h> |
14 | |
15 | /** |
16 | * EEH_SHOW_ATTR -- Create sysfs entry for eeh statistic |
17 | * @_name: name of file in sysfs directory |
18 | * @_memb: name of member in struct eeh_dev to access |
19 | * @_format: printf format for display |
20 | * |
21 | * All of the attributes look very similar, so just |
22 | * auto-gen a cut-n-paste routine to display them. |
23 | */ |
24 | #define EEH_SHOW_ATTR(_name,_memb,_format) \ |
25 | static ssize_t eeh_show_##_name(struct device *dev, \ |
26 | struct device_attribute *attr, char *buf) \ |
27 | { \ |
28 | struct pci_dev *pdev = to_pci_dev(dev); \ |
29 | struct eeh_dev *edev = pci_dev_to_eeh_dev(pdev); \ |
30 | \ |
31 | if (!edev) \ |
32 | return 0; \ |
33 | \ |
34 | return sprintf(buf, _format "\n", edev->_memb); \ |
35 | } \ |
36 | static DEVICE_ATTR(_name, 0444, eeh_show_##_name, NULL); |
37 | |
38 | EEH_SHOW_ATTR(eeh_mode, mode, "0x%x" ); |
39 | EEH_SHOW_ATTR(eeh_pe_config_addr, pe_config_addr, "0x%x" ); |
40 | |
41 | static ssize_t eeh_pe_state_show(struct device *dev, |
42 | struct device_attribute *attr, char *buf) |
43 | { |
44 | struct pci_dev *pdev = to_pci_dev(dev); |
45 | struct eeh_dev *edev = pci_dev_to_eeh_dev(pdev); |
46 | int state; |
47 | |
48 | if (!edev || !edev->pe) |
49 | return -ENODEV; |
50 | |
51 | state = eeh_ops->get_state(edev->pe, NULL); |
52 | return sprintf(buf, fmt: "0x%08x 0x%08x\n" , |
53 | state, edev->pe->state); |
54 | } |
55 | |
56 | static ssize_t eeh_pe_state_store(struct device *dev, |
57 | struct device_attribute *attr, |
58 | const char *buf, size_t count) |
59 | { |
60 | struct pci_dev *pdev = to_pci_dev(dev); |
61 | struct eeh_dev *edev = pci_dev_to_eeh_dev(pdev); |
62 | |
63 | if (!edev || !edev->pe) |
64 | return -ENODEV; |
65 | |
66 | /* Nothing to do if it's not frozen */ |
67 | if (!(edev->pe->state & EEH_PE_ISOLATED)) |
68 | return count; |
69 | |
70 | if (eeh_unfreeze_pe(edev->pe)) |
71 | return -EIO; |
72 | eeh_pe_state_clear(edev->pe, EEH_PE_ISOLATED, true); |
73 | |
74 | return count; |
75 | } |
76 | |
77 | static DEVICE_ATTR_RW(eeh_pe_state); |
78 | |
79 | #if defined(CONFIG_PCI_IOV) && defined(CONFIG_PPC_PSERIES) |
80 | static ssize_t eeh_notify_resume_show(struct device *dev, |
81 | struct device_attribute *attr, char *buf) |
82 | { |
83 | struct pci_dev *pdev = to_pci_dev(dev); |
84 | struct eeh_dev *edev = pci_dev_to_eeh_dev(pdev); |
85 | struct pci_dn *pdn = pci_get_pdn(pdev); |
86 | |
87 | if (!edev || !edev->pe) |
88 | return -ENODEV; |
89 | |
90 | return sprintf(buf, "%d\n" , pdn->last_allow_rc); |
91 | } |
92 | |
93 | static ssize_t eeh_notify_resume_store(struct device *dev, |
94 | struct device_attribute *attr, |
95 | const char *buf, size_t count) |
96 | { |
97 | struct pci_dev *pdev = to_pci_dev(dev); |
98 | struct eeh_dev *edev = pci_dev_to_eeh_dev(pdev); |
99 | |
100 | if (!edev || !edev->pe || !eeh_ops->notify_resume) |
101 | return -ENODEV; |
102 | |
103 | if (eeh_ops->notify_resume(edev)) |
104 | return -EIO; |
105 | |
106 | return count; |
107 | } |
108 | static DEVICE_ATTR_RW(eeh_notify_resume); |
109 | |
110 | static int eeh_notify_resume_add(struct pci_dev *pdev) |
111 | { |
112 | struct device_node *np; |
113 | int rc = 0; |
114 | |
115 | np = pci_device_to_OF_node(pdev->is_physfn ? pdev : pdev->physfn); |
116 | |
117 | if (of_property_read_bool(np, "ibm,is-open-sriov-pf" )) |
118 | rc = device_create_file(&pdev->dev, &dev_attr_eeh_notify_resume); |
119 | |
120 | return rc; |
121 | } |
122 | |
123 | static void eeh_notify_resume_remove(struct pci_dev *pdev) |
124 | { |
125 | struct device_node *np; |
126 | |
127 | np = pci_device_to_OF_node(pdev->is_physfn ? pdev : pdev->physfn); |
128 | |
129 | if (of_property_read_bool(np, "ibm,is-open-sriov-pf" )) |
130 | device_remove_file(&pdev->dev, &dev_attr_eeh_notify_resume); |
131 | } |
132 | #else |
133 | static inline int eeh_notify_resume_add(struct pci_dev *pdev) { return 0; } |
134 | static inline void eeh_notify_resume_remove(struct pci_dev *pdev) { } |
135 | #endif /* CONFIG_PCI_IOV && CONFIG PPC_PSERIES*/ |
136 | |
137 | void eeh_sysfs_add_device(struct pci_dev *pdev) |
138 | { |
139 | struct eeh_dev *edev = pci_dev_to_eeh_dev(pdev); |
140 | int rc=0; |
141 | |
142 | if (!eeh_enabled()) |
143 | return; |
144 | |
145 | if (edev && (edev->mode & EEH_DEV_SYSFS)) |
146 | return; |
147 | |
148 | rc += device_create_file(device: &pdev->dev, entry: &dev_attr_eeh_mode); |
149 | rc += device_create_file(device: &pdev->dev, entry: &dev_attr_eeh_pe_config_addr); |
150 | rc += device_create_file(device: &pdev->dev, entry: &dev_attr_eeh_pe_state); |
151 | rc += eeh_notify_resume_add(pdev); |
152 | |
153 | if (rc) |
154 | pr_warn("EEH: Unable to create sysfs entries\n" ); |
155 | else if (edev) |
156 | edev->mode |= EEH_DEV_SYSFS; |
157 | } |
158 | |
159 | void eeh_sysfs_remove_device(struct pci_dev *pdev) |
160 | { |
161 | struct eeh_dev *edev = pci_dev_to_eeh_dev(pdev); |
162 | |
163 | if (!edev) { |
164 | WARN_ON(eeh_enabled()); |
165 | return; |
166 | } |
167 | |
168 | edev->mode &= ~EEH_DEV_SYSFS; |
169 | |
170 | /* |
171 | * The parent directory might have been removed. We needn't |
172 | * continue for that case. |
173 | */ |
174 | if (!pdev->dev.kobj.sd) |
175 | return; |
176 | |
177 | device_remove_file(dev: &pdev->dev, attr: &dev_attr_eeh_mode); |
178 | device_remove_file(dev: &pdev->dev, attr: &dev_attr_eeh_pe_config_addr); |
179 | device_remove_file(dev: &pdev->dev, attr: &dev_attr_eeh_pe_state); |
180 | |
181 | eeh_notify_resume_remove(pdev); |
182 | } |
183 | |