]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/blame - drivers/pci/vpd.c
PCI/VPD: Move VPD access code to vpd.c
[mirror_ubuntu-hirsute-kernel.git] / drivers / pci / vpd.c
CommitLineData
7328c8f4 1// SPDX-License-Identifier: GPL-2.0
b55ac1b2
MC
2/*
3 * File: vpd.c
4 * Purpose: Provide PCI VPD support
5 *
6 * Copyright (C) 2010 Broadcom Corporation.
7 */
8
9#include <linux/pci.h>
f0eb77ae 10#include <linux/delay.h>
363c75db 11#include <linux/export.h>
f0eb77ae
BH
12#include <linux/sched/signal.h>
13#include "pci.h"
14
15/* VPD access through PCI 2.2+ VPD capability */
16
17/**
18 * pci_read_vpd - Read one entry from Vital Product Data
19 * @dev: pci device struct
20 * @pos: offset in vpd space
21 * @count: number of bytes to read
22 * @buf: pointer to where to store result
23 */
24ssize_t pci_read_vpd(struct pci_dev *dev, loff_t pos, size_t count, void *buf)
25{
26 if (!dev->vpd || !dev->vpd->ops)
27 return -ENODEV;
28 return dev->vpd->ops->read(dev, pos, count, buf);
29}
30EXPORT_SYMBOL(pci_read_vpd);
31
32/**
33 * pci_write_vpd - Write entry to Vital Product Data
34 * @dev: pci device struct
35 * @pos: offset in vpd space
36 * @count: number of bytes to write
37 * @buf: buffer containing write data
38 */
39ssize_t pci_write_vpd(struct pci_dev *dev, loff_t pos, size_t count, const void *buf)
40{
41 if (!dev->vpd || !dev->vpd->ops)
42 return -ENODEV;
43 return dev->vpd->ops->write(dev, pos, count, buf);
44}
45EXPORT_SYMBOL(pci_write_vpd);
46
47/**
48 * pci_set_vpd_size - Set size of Vital Product Data space
49 * @dev: pci device struct
50 * @len: size of vpd space
51 */
52int pci_set_vpd_size(struct pci_dev *dev, size_t len)
53{
54 if (!dev->vpd || !dev->vpd->ops)
55 return -ENODEV;
56 return dev->vpd->ops->set_size(dev, len);
57}
58EXPORT_SYMBOL(pci_set_vpd_size);
59
60#define PCI_VPD_MAX_SIZE (PCI_VPD_ADDR_MASK + 1)
61
62/**
63 * pci_vpd_size - determine actual size of Vital Product Data
64 * @dev: pci device struct
65 * @old_size: current assumed size, also maximum allowed size
66 */
67static size_t pci_vpd_size(struct pci_dev *dev, size_t old_size)
68{
69 size_t off = 0;
70 unsigned char header[1+2]; /* 1 byte tag, 2 bytes length */
71
72 while (off < old_size &&
73 pci_read_vpd(dev, off, 1, header) == 1) {
74 unsigned char tag;
75
76 if (header[0] & PCI_VPD_LRDT) {
77 /* Large Resource Data Type Tag */
78 tag = pci_vpd_lrdt_tag(header);
79 /* Only read length from known tag items */
80 if ((tag == PCI_VPD_LTIN_ID_STRING) ||
81 (tag == PCI_VPD_LTIN_RO_DATA) ||
82 (tag == PCI_VPD_LTIN_RW_DATA)) {
83 if (pci_read_vpd(dev, off+1, 2,
84 &header[1]) != 2) {
85 pci_warn(dev, "invalid large VPD tag %02x size at offset %zu",
86 tag, off + 1);
87 return 0;
88 }
89 off += PCI_VPD_LRDT_TAG_SIZE +
90 pci_vpd_lrdt_size(header);
91 }
92 } else {
93 /* Short Resource Data Type Tag */
94 off += PCI_VPD_SRDT_TAG_SIZE +
95 pci_vpd_srdt_size(header);
96 tag = pci_vpd_srdt_tag(header);
97 }
98
99 if (tag == PCI_VPD_STIN_END) /* End tag descriptor */
100 return off;
101
102 if ((tag != PCI_VPD_LTIN_ID_STRING) &&
103 (tag != PCI_VPD_LTIN_RO_DATA) &&
104 (tag != PCI_VPD_LTIN_RW_DATA)) {
105 pci_warn(dev, "invalid %s VPD tag %02x at offset %zu",
106 (header[0] & PCI_VPD_LRDT) ? "large" : "short",
107 tag, off);
108 return 0;
109 }
110 }
111 return 0;
112}
113
114/*
115 * Wait for last operation to complete.
116 * This code has to spin since there is no other notification from the PCI
117 * hardware. Since the VPD is often implemented by serial attachment to an
118 * EEPROM, it may take many milliseconds to complete.
119 *
120 * Returns 0 on success, negative values indicate error.
121 */
122static int pci_vpd_wait(struct pci_dev *dev)
123{
124 struct pci_vpd *vpd = dev->vpd;
125 unsigned long timeout = jiffies + msecs_to_jiffies(125);
126 unsigned long max_sleep = 16;
127 u16 status;
128 int ret;
129
130 if (!vpd->busy)
131 return 0;
132
133 while (time_before(jiffies, timeout)) {
134 ret = pci_user_read_config_word(dev, vpd->cap + PCI_VPD_ADDR,
135 &status);
136 if (ret < 0)
137 return ret;
138
139 if ((status & PCI_VPD_ADDR_F) == vpd->flag) {
140 vpd->busy = 0;
141 return 0;
142 }
143
144 if (fatal_signal_pending(current))
145 return -EINTR;
146
147 usleep_range(10, max_sleep);
148 if (max_sleep < 1024)
149 max_sleep *= 2;
150 }
151
152 pci_warn(dev, "VPD access failed. This is likely a firmware bug on this device. Contact the card vendor for a firmware update\n");
153 return -ETIMEDOUT;
154}
155
156static ssize_t pci_vpd_read(struct pci_dev *dev, loff_t pos, size_t count,
157 void *arg)
158{
159 struct pci_vpd *vpd = dev->vpd;
160 int ret;
161 loff_t end = pos + count;
162 u8 *buf = arg;
163
164 if (pos < 0)
165 return -EINVAL;
166
167 if (!vpd->valid) {
168 vpd->valid = 1;
169 vpd->len = pci_vpd_size(dev, vpd->len);
170 }
171
172 if (vpd->len == 0)
173 return -EIO;
174
175 if (pos > vpd->len)
176 return 0;
177
178 if (end > vpd->len) {
179 end = vpd->len;
180 count = end - pos;
181 }
182
183 if (mutex_lock_killable(&vpd->lock))
184 return -EINTR;
185
186 ret = pci_vpd_wait(dev);
187 if (ret < 0)
188 goto out;
189
190 while (pos < end) {
191 u32 val;
192 unsigned int i, skip;
193
194 ret = pci_user_write_config_word(dev, vpd->cap + PCI_VPD_ADDR,
195 pos & ~3);
196 if (ret < 0)
197 break;
198 vpd->busy = 1;
199 vpd->flag = PCI_VPD_ADDR_F;
200 ret = pci_vpd_wait(dev);
201 if (ret < 0)
202 break;
203
204 ret = pci_user_read_config_dword(dev, vpd->cap + PCI_VPD_DATA, &val);
205 if (ret < 0)
206 break;
207
208 skip = pos & 3;
209 for (i = 0; i < sizeof(u32); i++) {
210 if (i >= skip) {
211 *buf++ = val;
212 if (++pos == end)
213 break;
214 }
215 val >>= 8;
216 }
217 }
218out:
219 mutex_unlock(&vpd->lock);
220 return ret ? ret : count;
221}
222
223static ssize_t pci_vpd_write(struct pci_dev *dev, loff_t pos, size_t count,
224 const void *arg)
225{
226 struct pci_vpd *vpd = dev->vpd;
227 const u8 *buf = arg;
228 loff_t end = pos + count;
229 int ret = 0;
230
231 if (pos < 0 || (pos & 3) || (count & 3))
232 return -EINVAL;
233
234 if (!vpd->valid) {
235 vpd->valid = 1;
236 vpd->len = pci_vpd_size(dev, vpd->len);
237 }
238
239 if (vpd->len == 0)
240 return -EIO;
241
242 if (end > vpd->len)
243 return -EINVAL;
244
245 if (mutex_lock_killable(&vpd->lock))
246 return -EINTR;
247
248 ret = pci_vpd_wait(dev);
249 if (ret < 0)
250 goto out;
251
252 while (pos < end) {
253 u32 val;
254
255 val = *buf++;
256 val |= *buf++ << 8;
257 val |= *buf++ << 16;
258 val |= *buf++ << 24;
259
260 ret = pci_user_write_config_dword(dev, vpd->cap + PCI_VPD_DATA, val);
261 if (ret < 0)
262 break;
263 ret = pci_user_write_config_word(dev, vpd->cap + PCI_VPD_ADDR,
264 pos | PCI_VPD_ADDR_F);
265 if (ret < 0)
266 break;
267
268 vpd->busy = 1;
269 vpd->flag = 0;
270 ret = pci_vpd_wait(dev);
271 if (ret < 0)
272 break;
273
274 pos += sizeof(u32);
275 }
276out:
277 mutex_unlock(&vpd->lock);
278 return ret ? ret : count;
279}
280
281static int pci_vpd_set_size(struct pci_dev *dev, size_t len)
282{
283 struct pci_vpd *vpd = dev->vpd;
284
285 if (len == 0 || len > PCI_VPD_MAX_SIZE)
286 return -EIO;
287
288 vpd->valid = 1;
289 vpd->len = len;
290
291 return 0;
292}
293
294static const struct pci_vpd_ops pci_vpd_ops = {
295 .read = pci_vpd_read,
296 .write = pci_vpd_write,
297 .set_size = pci_vpd_set_size,
298};
299
300static ssize_t pci_vpd_f0_read(struct pci_dev *dev, loff_t pos, size_t count,
301 void *arg)
302{
303 struct pci_dev *tdev = pci_get_slot(dev->bus,
304 PCI_DEVFN(PCI_SLOT(dev->devfn), 0));
305 ssize_t ret;
306
307 if (!tdev)
308 return -ENODEV;
309
310 ret = pci_read_vpd(tdev, pos, count, arg);
311 pci_dev_put(tdev);
312 return ret;
313}
314
315static ssize_t pci_vpd_f0_write(struct pci_dev *dev, loff_t pos, size_t count,
316 const void *arg)
317{
318 struct pci_dev *tdev = pci_get_slot(dev->bus,
319 PCI_DEVFN(PCI_SLOT(dev->devfn), 0));
320 ssize_t ret;
321
322 if (!tdev)
323 return -ENODEV;
324
325 ret = pci_write_vpd(tdev, pos, count, arg);
326 pci_dev_put(tdev);
327 return ret;
328}
329
330static int pci_vpd_f0_set_size(struct pci_dev *dev, size_t len)
331{
332 struct pci_dev *tdev = pci_get_slot(dev->bus,
333 PCI_DEVFN(PCI_SLOT(dev->devfn), 0));
334 int ret;
335
336 if (!tdev)
337 return -ENODEV;
338
339 ret = pci_set_vpd_size(tdev, len);
340 pci_dev_put(tdev);
341 return ret;
342}
343
344static const struct pci_vpd_ops pci_vpd_f0_ops = {
345 .read = pci_vpd_f0_read,
346 .write = pci_vpd_f0_write,
347 .set_size = pci_vpd_f0_set_size,
348};
349
350int pci_vpd_init(struct pci_dev *dev)
351{
352 struct pci_vpd *vpd;
353 u8 cap;
354
355 cap = pci_find_capability(dev, PCI_CAP_ID_VPD);
356 if (!cap)
357 return -ENODEV;
358
359 vpd = kzalloc(sizeof(*vpd), GFP_ATOMIC);
360 if (!vpd)
361 return -ENOMEM;
362
363 vpd->len = PCI_VPD_MAX_SIZE;
364 if (dev->dev_flags & PCI_DEV_FLAGS_VPD_REF_F0)
365 vpd->ops = &pci_vpd_f0_ops;
366 else
367 vpd->ops = &pci_vpd_ops;
368 mutex_init(&vpd->lock);
369 vpd->cap = cap;
370 vpd->busy = 0;
371 vpd->valid = 0;
372 dev->vpd = vpd;
373 return 0;
374}
375
376void pci_vpd_release(struct pci_dev *dev)
377{
378 kfree(dev->vpd);
379}
b55ac1b2
MC
380
381int pci_vpd_find_tag(const u8 *buf, unsigned int off, unsigned int len, u8 rdt)
382{
383 int i;
384
385 for (i = off; i < len; ) {
386 u8 val = buf[i];
387
388 if (val & PCI_VPD_LRDT) {
389 /* Don't return success of the tag isn't complete */
390 if (i + PCI_VPD_LRDT_TAG_SIZE > len)
391 break;
392
393 if (val == rdt)
394 return i;
395
396 i += PCI_VPD_LRDT_TAG_SIZE +
397 pci_vpd_lrdt_size(&buf[i]);
398 } else {
399 u8 tag = val & ~PCI_VPD_SRDT_LEN_MASK;
400
401 if (tag == rdt)
402 return i;
403
404 if (tag == PCI_VPD_SRDT_END)
405 break;
406
407 i += PCI_VPD_SRDT_TAG_SIZE +
408 pci_vpd_srdt_size(&buf[i]);
409 }
410 }
411
412 return -ENOENT;
413}
414EXPORT_SYMBOL_GPL(pci_vpd_find_tag);
4067a854
MC
415
416int pci_vpd_find_info_keyword(const u8 *buf, unsigned int off,
417 unsigned int len, const char *kw)
418{
419 int i;
420
421 for (i = off; i + PCI_VPD_INFO_FLD_HDR_SIZE <= off + len;) {
422 if (buf[i + 0] == kw[0] &&
423 buf[i + 1] == kw[1])
424 return i;
425
426 i += PCI_VPD_INFO_FLD_HDR_SIZE +
427 pci_vpd_info_field_size(&buf[i]);
428 }
429
430 return -ENOENT;
431}
432EXPORT_SYMBOL_GPL(pci_vpd_find_info_keyword);