diff options
Diffstat (limited to 'drivers/pci/access.c')
-rw-r--r-- | drivers/pci/access.c | 166 |
1 files changed, 166 insertions, 0 deletions
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 |