]>
Commit | Line | Data |
---|---|---|
11f7b31b MM |
1 | /* |
2 | * The PCI Library -- Configuration Access via /sys/bus/pci | |
3 | * | |
535fca0e | 4 | * Copyright (c) 2003 Matthew Wilcox <matthew@wil.cx> |
cb6ee324 | 5 | * Copyright (c) 1997--2008 Martin Mares <mj@ucw.cz> |
11f7b31b MM |
6 | * |
7 | * Can be freely distributed and used under the terms of the GNU GPL. | |
8 | */ | |
9 | ||
10 | #define _GNU_SOURCE | |
11 | ||
12 | #include <stdio.h> | |
13 | #include <stdlib.h> | |
14 | #include <string.h> | |
15 | #include <stdarg.h> | |
16 | #include <unistd.h> | |
17 | #include <errno.h> | |
18 | #include <dirent.h> | |
19 | #include <fcntl.h> | |
20 | #include <sys/types.h> | |
21 | ||
22 | #include "internal.h" | |
23 | #include "pread.h" | |
24 | ||
25 | static void | |
26 | sysfs_config(struct pci_access *a) | |
27 | { | |
cb6ee324 | 28 | pci_define_param(a, "sysfs.path", PCI_PATH_SYS_BUS_PCI, "Path to the sysfs device tree"); |
11f7b31b MM |
29 | } |
30 | ||
31 | static inline char * | |
32 | sysfs_name(struct pci_access *a) | |
33 | { | |
cb6ee324 | 34 | return pci_get_param(a, "sysfs.path"); |
11f7b31b MM |
35 | } |
36 | ||
37 | static int | |
38 | sysfs_detect(struct pci_access *a) | |
39 | { | |
40 | if (access(sysfs_name(a), R_OK)) | |
41 | { | |
84c8d1bb | 42 | a->debug("...cannot open %s", sysfs_name(a)); |
11f7b31b MM |
43 | return 0; |
44 | } | |
45 | a->debug("...using %s", sysfs_name(a)); | |
46 | return 1; | |
47 | } | |
48 | ||
49 | static void | |
50 | sysfs_init(struct pci_access *a) | |
51 | { | |
52 | a->fd = -1; | |
07ad085d | 53 | a->fd_vpd = -1; |
11f7b31b MM |
54 | } |
55 | ||
56 | static void | |
07ad085d | 57 | sysfs_flush_cache(struct pci_access *a) |
11f7b31b MM |
58 | { |
59 | if (a->fd >= 0) | |
60 | { | |
61 | close(a->fd); | |
62 | a->fd = -1; | |
63 | } | |
07ad085d MM |
64 | if (a->fd_vpd >= 0) |
65 | { | |
66 | close(a->fd_vpd); | |
67 | a->fd_vpd = -1; | |
68 | } | |
69 | a->cached_dev = NULL; | |
70 | } | |
71 | ||
72 | static void | |
73 | sysfs_cleanup(struct pci_access *a) | |
74 | { | |
75 | sysfs_flush_cache(a); | |
11f7b31b MM |
76 | } |
77 | ||
78 | #define OBJNAMELEN 1024 | |
79 | static void | |
80 | sysfs_obj_name(struct pci_dev *d, char *object, char *buf) | |
81 | { | |
82 | int n = snprintf(buf, OBJNAMELEN, "%s/devices/%04x:%02x:%02x.%d/%s", | |
83 | sysfs_name(d->access), d->domain, d->bus, d->dev, d->func, object); | |
84 | if (n < 0 || n >= OBJNAMELEN) | |
85 | d->access->error("File name too long"); | |
86 | } | |
87 | ||
b1861a8d MM |
88 | #define OBJBUFSIZE 1024 |
89 | ||
11f7b31b | 90 | static int |
b1861a8d | 91 | sysfs_get_string(struct pci_dev *d, char *object, char *buf, int mandatory) |
11f7b31b MM |
92 | { |
93 | struct pci_access *a = d->access; | |
94 | int fd, n; | |
b1861a8d | 95 | char namebuf[OBJNAMELEN]; |
11f7b31b MM |
96 | |
97 | sysfs_obj_name(d, object, namebuf); | |
98 | fd = open(namebuf, O_RDONLY); | |
99 | if (fd < 0) | |
b1861a8d MM |
100 | { |
101 | if (mandatory) | |
102 | a->error("Cannot open %s: %s", namebuf, strerror(errno)); | |
103 | return 0; | |
104 | } | |
105 | n = read(fd, buf, OBJBUFSIZE); | |
11f7b31b MM |
106 | close(fd); |
107 | if (n < 0) | |
108 | a->error("Error reading %s: %s", namebuf, strerror(errno)); | |
b1861a8d | 109 | if (n >= OBJBUFSIZE) |
11f7b31b MM |
110 | a->error("Value in %s too long", namebuf); |
111 | buf[n] = 0; | |
b1861a8d MM |
112 | return 1; |
113 | } | |
114 | ||
115 | static int | |
116 | sysfs_get_value(struct pci_dev *d, char *object) | |
117 | { | |
118 | char buf[OBJBUFSIZE]; | |
119 | ||
120 | sysfs_get_string(d, object, buf, 1); | |
11f7b31b MM |
121 | return strtol(buf, NULL, 0); |
122 | } | |
123 | ||
124 | static void | |
125 | sysfs_get_resources(struct pci_dev *d) | |
126 | { | |
127 | struct pci_access *a = d->access; | |
128 | char namebuf[OBJNAMELEN], buf[256]; | |
129 | FILE *file; | |
130 | int i; | |
131 | ||
132 | sysfs_obj_name(d, "resource", namebuf); | |
133 | file = fopen(namebuf, "r"); | |
134 | if (!file) | |
135 | a->error("Cannot open %s: %s", namebuf, strerror(errno)); | |
9bb4b4ea | 136 | for (i = 0; i < 7; i++) |
11f7b31b | 137 | { |
6d143c32 | 138 | unsigned long long start, end, size, flags; |
11f7b31b MM |
139 | if (!fgets(buf, sizeof(buf), file)) |
140 | break; | |
6d143c32 | 141 | if (sscanf(buf, "%llx %llx %llx", &start, &end, &flags) != 3) |
11f7b31b | 142 | a->error("Syntax error in %s", namebuf); |
00bf6625 | 143 | if (end > start) |
11f7b31b MM |
144 | size = end - start + 1; |
145 | else | |
146 | size = 0; | |
6d143c32 | 147 | flags &= PCI_ADDR_FLAG_MASK; |
9bb4b4ea | 148 | if (i < 6) |
11f7b31b | 149 | { |
6d143c32 | 150 | d->base_addr[i] = start | flags; |
11f7b31b MM |
151 | d->size[i] = size; |
152 | } | |
153 | else | |
154 | { | |
6d143c32 | 155 | d->rom_base_addr = start | flags; |
11f7b31b MM |
156 | d->rom_size = size; |
157 | } | |
158 | } | |
159 | fclose(file); | |
160 | } | |
161 | ||
162 | static void sysfs_scan(struct pci_access *a) | |
163 | { | |
73e87e96 | 164 | char dirname[1024], buf[OBJBUFSIZE]; |
11f7b31b MM |
165 | DIR *dir; |
166 | struct dirent *entry; | |
167 | int n; | |
168 | ||
169 | n = snprintf(dirname, sizeof(dirname), "%s/devices", sysfs_name(a)); | |
170 | if (n < 0 || n >= (int) sizeof(dirname)) | |
171 | a->error("Directory name too long"); | |
172 | dir = opendir(dirname); | |
173 | if (!dir) | |
174 | a->error("Cannot open %s", dirname); | |
175 | while ((entry = readdir(dir))) | |
176 | { | |
177 | struct pci_dev *d; | |
178 | unsigned int dom, bus, dev, func; | |
179 | ||
180 | /* ".", ".." or a special non-device perhaps */ | |
181 | if (entry->d_name[0] == '.') | |
182 | continue; | |
183 | ||
184 | d = pci_alloc_dev(a); | |
185 | if (sscanf(entry->d_name, "%x:%x:%x.%d", &dom, &bus, &dev, &func) < 4) | |
186 | a->error("sysfs_scan: Couldn't parse entry name %s", entry->d_name); | |
187 | d->domain = dom; | |
188 | d->bus = bus; | |
189 | d->dev = dev; | |
190 | d->func = func; | |
11f7b31b MM |
191 | if (!a->buscentric) |
192 | { | |
193 | sysfs_get_resources(d); | |
84c8d1bb | 194 | d->irq = sysfs_get_value(d, "irq"); |
84c8d1bb | 195 | /* |
c2b144ef MM |
196 | * We could read these faster from the config registers, but we want to give |
197 | * the kernel a chance to fix up ID's and especially classes of broken devices. | |
84c8d1bb | 198 | */ |
11f7b31b MM |
199 | d->vendor_id = sysfs_get_value(d, "vendor"); |
200 | d->device_id = sysfs_get_value(d, "device"); | |
c2b144ef | 201 | d->device_class = sysfs_get_value(d, "class") >> 8; |
aecf5b35 TR |
202 | if (sysfs_get_string(d, "label", buf, 0)) |
203 | d->label = pci_strdup(d->access, buf); | |
204 | ||
c2b144ef | 205 | d->known_fields = PCI_FILL_IDENT | PCI_FILL_CLASS | PCI_FILL_IRQ | PCI_FILL_BASES | PCI_FILL_ROM_BASE | PCI_FILL_SIZES; |
11f7b31b MM |
206 | } |
207 | pci_link_dev(a, d); | |
208 | } | |
209 | closedir(dir); | |
210 | } | |
211 | ||
2849a165 | 212 | static void |
6171c06d | 213 | sysfs_fill_slots(struct pci_access *a) |
2849a165 | 214 | { |
2849a165 AC |
215 | char dirname[1024]; |
216 | DIR *dir; | |
217 | struct dirent *entry; | |
218 | int n; | |
219 | ||
220 | n = snprintf(dirname, sizeof(dirname), "%s/slots", sysfs_name(a)); | |
221 | if (n < 0 || n >= (int) sizeof(dirname)) | |
222 | a->error("Directory name too long"); | |
223 | dir = opendir(dirname); | |
224 | if (!dir) | |
6171c06d MM |
225 | return; |
226 | ||
227 | while (entry = readdir(dir)) | |
2849a165 AC |
228 | { |
229 | char namebuf[OBJNAMELEN], buf[16]; | |
230 | FILE *file; | |
231 | unsigned int dom, bus, dev; | |
4134538a | 232 | int res = 0; |
6171c06d | 233 | struct pci_dev *d; |
2849a165 AC |
234 | |
235 | /* ".", ".." or a special non-device perhaps */ | |
236 | if (entry->d_name[0] == '.') | |
237 | continue; | |
238 | ||
6171c06d | 239 | n = snprintf(namebuf, OBJNAMELEN, "%s/%s/%s", dirname, entry->d_name, "address"); |
2849a165 | 240 | if (n < 0 || n >= OBJNAMELEN) |
6171c06d | 241 | a->error("File name too long"); |
2849a165 | 242 | file = fopen(namebuf, "r"); |
534fbba9 MW |
243 | /* |
244 | * Old versions of Linux had a fakephp which didn't have an 'address' | |
245 | * file. There's no useful information to be gleaned from these | |
246 | * devices, pretend they're not there. | |
247 | */ | |
2849a165 | 248 | if (!file) |
534fbba9 | 249 | continue; |
6171c06d | 250 | |
4134538a MM |
251 | if (!fgets(buf, sizeof(buf), file) || (res = sscanf(buf, "%x:%x:%x", &dom, &bus, &dev)) < 3) |
252 | { | |
253 | /* | |
254 | * In some cases, the slot is not tied to a specific device before | |
255 | * a card gets inserted. This happens for example on IBM pSeries | |
256 | * and we need not warn about it. | |
257 | */ | |
258 | if (res != 2) | |
259 | a->warning("sysfs_fill_slots: Couldn't parse entry address %s", buf); | |
260 | } | |
6171c06d MM |
261 | else |
262 | { | |
263 | for (d = a->devices; d; d = d->next) | |
264 | if (dom == d->domain && bus == d->bus && dev == d->dev && !d->phy_slot) | |
b1861a8d | 265 | d->phy_slot = pci_strdup(a, entry->d_name); |
2849a165 AC |
266 | } |
267 | fclose(file); | |
268 | } | |
269 | closedir(dir); | |
270 | } | |
271 | ||
272 | static int | |
273 | sysfs_fill_info(struct pci_dev *d, int flags) | |
274 | { | |
6171c06d MM |
275 | if ((flags & PCI_FILL_PHYS_SLOT) && !(d->known_fields & PCI_FILL_PHYS_SLOT)) |
276 | { | |
277 | struct pci_dev *pd; | |
278 | sysfs_fill_slots(d->access); | |
279 | for (pd = d->access->devices; pd; pd = pd->next) | |
280 | pd->known_fields |= PCI_FILL_PHYS_SLOT; | |
281 | } | |
b1861a8d MM |
282 | |
283 | if ((flags & PCI_FILL_MODULE_ALIAS) && !(d->known_fields & PCI_FILL_MODULE_ALIAS)) | |
284 | { | |
285 | char buf[OBJBUFSIZE]; | |
286 | if (sysfs_get_string(d, "modalias", buf, 0)) | |
287 | d->module_alias = pci_strdup(d->access, buf); | |
288 | } | |
289 | ||
6171c06d | 290 | return pci_generic_fill_info(d, flags); |
2849a165 AC |
291 | } |
292 | ||
9bd5b1cf BH |
293 | /* Intent of the sysfs_setup() caller */ |
294 | enum | |
295 | { | |
296 | SETUP_READ_CONFIG = 0, | |
297 | SETUP_WRITE_CONFIG = 1, | |
298 | SETUP_READ_VPD = 2 | |
299 | }; | |
300 | ||
11f7b31b | 301 | static int |
9bd5b1cf | 302 | sysfs_setup(struct pci_dev *d, int intent) |
11f7b31b MM |
303 | { |
304 | struct pci_access *a = d->access; | |
9bd5b1cf | 305 | char namebuf[OBJNAMELEN]; |
11f7b31b | 306 | |
07ad085d | 307 | if (a->cached_dev != d || (intent == SETUP_WRITE_CONFIG && !a->fd_rw)) |
11f7b31b | 308 | { |
07ad085d MM |
309 | sysfs_flush_cache(a); |
310 | a->cached_dev = d; | |
11f7b31b | 311 | } |
9bd5b1cf | 312 | |
07ad085d | 313 | if (intent == SETUP_READ_VPD) |
9bd5b1cf | 314 | { |
07ad085d MM |
315 | if (a->fd_vpd < 0) |
316 | { | |
317 | sysfs_obj_name(d, "vpd", namebuf); | |
318 | a->fd_vpd = open(namebuf, O_RDONLY); | |
319 | /* No warning on error; vpd may be absent or accessible only to root */ | |
320 | } | |
321 | return a->fd_vpd; | |
9bd5b1cf BH |
322 | } |
323 | ||
07ad085d | 324 | if (a->fd < 0) |
c886948c MM |
325 | { |
326 | sysfs_obj_name(d, "config", namebuf); | |
327 | a->fd_rw = a->writeable || intent == SETUP_WRITE_CONFIG; | |
328 | a->fd = open(namebuf, a->fd_rw ? O_RDWR : O_RDONLY); | |
329 | if (a->fd < 0) | |
330 | a->warning("Cannot open %s", namebuf); | |
331 | a->fd_pos = 0; | |
332 | } | |
07ad085d | 333 | return a->fd; |
11f7b31b MM |
334 | } |
335 | ||
336 | static int sysfs_read(struct pci_dev *d, int pos, byte *buf, int len) | |
337 | { | |
9bd5b1cf | 338 | int fd = sysfs_setup(d, SETUP_READ_CONFIG); |
11f7b31b MM |
339 | int res; |
340 | ||
341 | if (fd < 0) | |
342 | return 0; | |
343 | res = do_read(d, fd, buf, len, pos); | |
344 | if (res < 0) | |
345 | { | |
346 | d->access->warning("sysfs_read: read failed: %s", strerror(errno)); | |
347 | return 0; | |
348 | } | |
349 | else if (res != len) | |
09817437 | 350 | return 0; |
11f7b31b MM |
351 | return 1; |
352 | } | |
353 | ||
354 | static int sysfs_write(struct pci_dev *d, int pos, byte *buf, int len) | |
355 | { | |
9bd5b1cf | 356 | int fd = sysfs_setup(d, SETUP_WRITE_CONFIG); |
11f7b31b MM |
357 | int res; |
358 | ||
359 | if (fd < 0) | |
360 | return 0; | |
361 | res = do_write(d, fd, buf, len, pos); | |
362 | if (res < 0) | |
363 | { | |
364 | d->access->warning("sysfs_write: write failed: %s", strerror(errno)); | |
365 | return 0; | |
366 | } | |
367 | else if (res != len) | |
368 | { | |
09817437 | 369 | d->access->warning("sysfs_write: tried to write %d bytes at %d, but only %d succeeded", len, pos, res); |
11f7b31b MM |
370 | return 0; |
371 | } | |
372 | return 1; | |
373 | } | |
374 | ||
9bd5b1cf BH |
375 | #ifdef PCI_HAVE_DO_READ |
376 | ||
377 | /* pread() is not available and do_read() only works for a single fd, so we | |
378 | * cannot implement read_vpd properly. */ | |
379 | static int sysfs_read_vpd(struct pci_dev *d, int pos, byte *buf, int len) | |
380 | { | |
381 | return 0; | |
382 | } | |
383 | ||
384 | #else /* !PCI_HAVE_DO_READ */ | |
385 | ||
386 | static int sysfs_read_vpd(struct pci_dev *d, int pos, byte *buf, int len) | |
387 | { | |
388 | int fd = sysfs_setup(d, SETUP_READ_VPD); | |
389 | int res; | |
390 | ||
391 | if (fd < 0) | |
392 | return 0; | |
393 | res = pread(fd, buf, len, pos); | |
394 | if (res < 0) | |
395 | { | |
396 | d->access->warning("sysfs_read_vpd: read failed: %s", strerror(errno)); | |
397 | return 0; | |
398 | } | |
399 | else if (res != len) | |
400 | return 0; | |
401 | return 1; | |
402 | } | |
403 | ||
404 | #endif /* PCI_HAVE_DO_READ */ | |
405 | ||
11f7b31b MM |
406 | static void sysfs_cleanup_dev(struct pci_dev *d) |
407 | { | |
408 | struct pci_access *a = d->access; | |
409 | ||
410 | if (a->cached_dev == d) | |
07ad085d | 411 | sysfs_flush_cache(a); |
11f7b31b MM |
412 | } |
413 | ||
414 | struct pci_methods pm_linux_sysfs = { | |
9ff67879 MM |
415 | "linux-sysfs", |
416 | "The sys filesystem on Linux", | |
11f7b31b MM |
417 | sysfs_config, |
418 | sysfs_detect, | |
419 | sysfs_init, | |
420 | sysfs_cleanup, | |
421 | sysfs_scan, | |
2849a165 | 422 | sysfs_fill_info, |
11f7b31b MM |
423 | sysfs_read, |
424 | sysfs_write, | |
9bd5b1cf | 425 | sysfs_read_vpd, |
11f7b31b MM |
426 | NULL, /* init_dev */ |
427 | sysfs_cleanup_dev | |
428 | }; |