]>
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 | ||
88 | static int | |
89 | sysfs_get_value(struct pci_dev *d, char *object) | |
90 | { | |
91 | struct pci_access *a = d->access; | |
92 | int fd, n; | |
93 | char namebuf[OBJNAMELEN], buf[256]; | |
94 | ||
95 | sysfs_obj_name(d, object, namebuf); | |
96 | fd = open(namebuf, O_RDONLY); | |
97 | if (fd < 0) | |
98 | a->error("Cannot open %s: %s", namebuf, strerror(errno)); | |
99 | n = read(fd, buf, sizeof(buf)); | |
100 | close(fd); | |
101 | if (n < 0) | |
102 | a->error("Error reading %s: %s", namebuf, strerror(errno)); | |
103 | if (n >= (int) sizeof(buf)) | |
104 | a->error("Value in %s too long", namebuf); | |
105 | buf[n] = 0; | |
106 | return strtol(buf, NULL, 0); | |
107 | } | |
108 | ||
109 | static void | |
110 | sysfs_get_resources(struct pci_dev *d) | |
111 | { | |
112 | struct pci_access *a = d->access; | |
113 | char namebuf[OBJNAMELEN], buf[256]; | |
114 | FILE *file; | |
115 | int i; | |
116 | ||
117 | sysfs_obj_name(d, "resource", namebuf); | |
118 | file = fopen(namebuf, "r"); | |
119 | if (!file) | |
120 | a->error("Cannot open %s: %s", namebuf, strerror(errno)); | |
9bb4b4ea | 121 | for (i = 0; i < 7; i++) |
11f7b31b | 122 | { |
6d143c32 | 123 | unsigned long long start, end, size, flags; |
11f7b31b MM |
124 | if (!fgets(buf, sizeof(buf), file)) |
125 | break; | |
6d143c32 | 126 | if (sscanf(buf, "%llx %llx %llx", &start, &end, &flags) != 3) |
11f7b31b | 127 | a->error("Syntax error in %s", namebuf); |
11f7b31b MM |
128 | if (start) |
129 | size = end - start + 1; | |
130 | else | |
131 | size = 0; | |
6d143c32 | 132 | flags &= PCI_ADDR_FLAG_MASK; |
9bb4b4ea | 133 | if (i < 6) |
11f7b31b | 134 | { |
6d143c32 | 135 | d->base_addr[i] = start | flags; |
11f7b31b MM |
136 | d->size[i] = size; |
137 | } | |
138 | else | |
139 | { | |
6d143c32 | 140 | d->rom_base_addr = start | flags; |
11f7b31b MM |
141 | d->rom_size = size; |
142 | } | |
143 | } | |
144 | fclose(file); | |
145 | } | |
146 | ||
147 | static void sysfs_scan(struct pci_access *a) | |
148 | { | |
149 | char dirname[1024]; | |
150 | DIR *dir; | |
151 | struct dirent *entry; | |
152 | int n; | |
153 | ||
154 | n = snprintf(dirname, sizeof(dirname), "%s/devices", sysfs_name(a)); | |
155 | if (n < 0 || n >= (int) sizeof(dirname)) | |
156 | a->error("Directory name too long"); | |
157 | dir = opendir(dirname); | |
158 | if (!dir) | |
159 | a->error("Cannot open %s", dirname); | |
160 | while ((entry = readdir(dir))) | |
161 | { | |
162 | struct pci_dev *d; | |
163 | unsigned int dom, bus, dev, func; | |
164 | ||
165 | /* ".", ".." or a special non-device perhaps */ | |
166 | if (entry->d_name[0] == '.') | |
167 | continue; | |
168 | ||
169 | d = pci_alloc_dev(a); | |
170 | if (sscanf(entry->d_name, "%x:%x:%x.%d", &dom, &bus, &dev, &func) < 4) | |
171 | a->error("sysfs_scan: Couldn't parse entry name %s", entry->d_name); | |
172 | d->domain = dom; | |
173 | d->bus = bus; | |
174 | d->dev = dev; | |
175 | d->func = func; | |
11f7b31b MM |
176 | if (!a->buscentric) |
177 | { | |
178 | sysfs_get_resources(d); | |
84c8d1bb | 179 | d->irq = sysfs_get_value(d, "irq"); |
84c8d1bb | 180 | /* |
c2b144ef MM |
181 | * We could read these faster from the config registers, but we want to give |
182 | * the kernel a chance to fix up ID's and especially classes of broken devices. | |
84c8d1bb | 183 | */ |
11f7b31b MM |
184 | d->vendor_id = sysfs_get_value(d, "vendor"); |
185 | d->device_id = sysfs_get_value(d, "device"); | |
c2b144ef MM |
186 | d->device_class = sysfs_get_value(d, "class") >> 8; |
187 | d->known_fields = PCI_FILL_IDENT | PCI_FILL_CLASS | PCI_FILL_IRQ | PCI_FILL_BASES | PCI_FILL_ROM_BASE | PCI_FILL_SIZES; | |
11f7b31b MM |
188 | } |
189 | pci_link_dev(a, d); | |
190 | } | |
191 | closedir(dir); | |
192 | } | |
193 | ||
2849a165 | 194 | static void |
6171c06d | 195 | sysfs_fill_slots(struct pci_access *a) |
2849a165 | 196 | { |
2849a165 AC |
197 | char dirname[1024]; |
198 | DIR *dir; | |
199 | struct dirent *entry; | |
200 | int n; | |
201 | ||
202 | n = snprintf(dirname, sizeof(dirname), "%s/slots", sysfs_name(a)); | |
203 | if (n < 0 || n >= (int) sizeof(dirname)) | |
204 | a->error("Directory name too long"); | |
205 | dir = opendir(dirname); | |
206 | if (!dir) | |
6171c06d MM |
207 | return; |
208 | ||
209 | while (entry = readdir(dir)) | |
2849a165 AC |
210 | { |
211 | char namebuf[OBJNAMELEN], buf[16]; | |
212 | FILE *file; | |
213 | unsigned int dom, bus, dev; | |
6171c06d | 214 | struct pci_dev *d; |
2849a165 AC |
215 | |
216 | /* ".", ".." or a special non-device perhaps */ | |
217 | if (entry->d_name[0] == '.') | |
218 | continue; | |
219 | ||
6171c06d | 220 | n = snprintf(namebuf, OBJNAMELEN, "%s/%s/%s", dirname, entry->d_name, "address"); |
2849a165 | 221 | if (n < 0 || n >= OBJNAMELEN) |
6171c06d | 222 | a->error("File name too long"); |
2849a165 AC |
223 | file = fopen(namebuf, "r"); |
224 | if (!file) | |
2849a165 | 225 | { |
6171c06d MM |
226 | a->warning("sysfs_fill_slots: Cannot open %s: %s", namebuf, strerror(errno)); |
227 | continue; | |
228 | } | |
229 | ||
230 | if (!fgets(buf, sizeof(buf), file) || sscanf(buf, "%x:%x:%x", &dom, &bus, &dev) < 3) | |
231 | a->warning("sysfs_fill_slots: Couldn't parse entry address %s", buf); | |
232 | else | |
233 | { | |
234 | for (d = a->devices; d; d = d->next) | |
235 | if (dom == d->domain && bus == d->bus && dev == d->dev && !d->phy_slot) | |
236 | { | |
237 | d->phy_slot = pci_malloc(a, strlen(entry->d_name) + 1); | |
238 | strcpy(d->phy_slot, entry->d_name); | |
239 | break; | |
240 | } | |
2849a165 AC |
241 | } |
242 | fclose(file); | |
243 | } | |
244 | closedir(dir); | |
245 | } | |
246 | ||
247 | static int | |
248 | sysfs_fill_info(struct pci_dev *d, int flags) | |
249 | { | |
6171c06d MM |
250 | if ((flags & PCI_FILL_PHYS_SLOT) && !(d->known_fields & PCI_FILL_PHYS_SLOT)) |
251 | { | |
252 | struct pci_dev *pd; | |
253 | sysfs_fill_slots(d->access); | |
254 | for (pd = d->access->devices; pd; pd = pd->next) | |
255 | pd->known_fields |= PCI_FILL_PHYS_SLOT; | |
256 | } | |
257 | return pci_generic_fill_info(d, flags); | |
2849a165 AC |
258 | } |
259 | ||
9bd5b1cf BH |
260 | /* Intent of the sysfs_setup() caller */ |
261 | enum | |
262 | { | |
263 | SETUP_READ_CONFIG = 0, | |
264 | SETUP_WRITE_CONFIG = 1, | |
265 | SETUP_READ_VPD = 2 | |
266 | }; | |
267 | ||
11f7b31b | 268 | static int |
9bd5b1cf | 269 | sysfs_setup(struct pci_dev *d, int intent) |
11f7b31b MM |
270 | { |
271 | struct pci_access *a = d->access; | |
9bd5b1cf | 272 | char namebuf[OBJNAMELEN]; |
11f7b31b | 273 | |
07ad085d | 274 | if (a->cached_dev != d || (intent == SETUP_WRITE_CONFIG && !a->fd_rw)) |
11f7b31b | 275 | { |
07ad085d MM |
276 | sysfs_flush_cache(a); |
277 | a->cached_dev = d; | |
11f7b31b | 278 | } |
9bd5b1cf | 279 | |
07ad085d | 280 | if (intent == SETUP_READ_VPD) |
9bd5b1cf | 281 | { |
07ad085d MM |
282 | if (a->fd_vpd < 0) |
283 | { | |
284 | sysfs_obj_name(d, "vpd", namebuf); | |
285 | a->fd_vpd = open(namebuf, O_RDONLY); | |
286 | /* No warning on error; vpd may be absent or accessible only to root */ | |
287 | } | |
288 | return a->fd_vpd; | |
9bd5b1cf BH |
289 | } |
290 | ||
07ad085d | 291 | if (a->fd < 0) |
c886948c MM |
292 | { |
293 | sysfs_obj_name(d, "config", namebuf); | |
294 | a->fd_rw = a->writeable || intent == SETUP_WRITE_CONFIG; | |
295 | a->fd = open(namebuf, a->fd_rw ? O_RDWR : O_RDONLY); | |
296 | if (a->fd < 0) | |
297 | a->warning("Cannot open %s", namebuf); | |
298 | a->fd_pos = 0; | |
299 | } | |
07ad085d | 300 | return a->fd; |
11f7b31b MM |
301 | } |
302 | ||
303 | static int sysfs_read(struct pci_dev *d, int pos, byte *buf, int len) | |
304 | { | |
9bd5b1cf | 305 | int fd = sysfs_setup(d, SETUP_READ_CONFIG); |
11f7b31b MM |
306 | int res; |
307 | ||
308 | if (fd < 0) | |
309 | return 0; | |
310 | res = do_read(d, fd, buf, len, pos); | |
311 | if (res < 0) | |
312 | { | |
313 | d->access->warning("sysfs_read: read failed: %s", strerror(errno)); | |
314 | return 0; | |
315 | } | |
316 | else if (res != len) | |
09817437 | 317 | return 0; |
11f7b31b MM |
318 | return 1; |
319 | } | |
320 | ||
321 | static int sysfs_write(struct pci_dev *d, int pos, byte *buf, int len) | |
322 | { | |
9bd5b1cf | 323 | int fd = sysfs_setup(d, SETUP_WRITE_CONFIG); |
11f7b31b MM |
324 | int res; |
325 | ||
326 | if (fd < 0) | |
327 | return 0; | |
328 | res = do_write(d, fd, buf, len, pos); | |
329 | if (res < 0) | |
330 | { | |
331 | d->access->warning("sysfs_write: write failed: %s", strerror(errno)); | |
332 | return 0; | |
333 | } | |
334 | else if (res != len) | |
335 | { | |
09817437 | 336 | d->access->warning("sysfs_write: tried to write %d bytes at %d, but only %d succeeded", len, pos, res); |
11f7b31b MM |
337 | return 0; |
338 | } | |
339 | return 1; | |
340 | } | |
341 | ||
9bd5b1cf BH |
342 | #ifdef PCI_HAVE_DO_READ |
343 | ||
344 | /* pread() is not available and do_read() only works for a single fd, so we | |
345 | * cannot implement read_vpd properly. */ | |
346 | static int sysfs_read_vpd(struct pci_dev *d, int pos, byte *buf, int len) | |
347 | { | |
348 | return 0; | |
349 | } | |
350 | ||
351 | #else /* !PCI_HAVE_DO_READ */ | |
352 | ||
353 | static int sysfs_read_vpd(struct pci_dev *d, int pos, byte *buf, int len) | |
354 | { | |
355 | int fd = sysfs_setup(d, SETUP_READ_VPD); | |
356 | int res; | |
357 | ||
358 | if (fd < 0) | |
359 | return 0; | |
360 | res = pread(fd, buf, len, pos); | |
361 | if (res < 0) | |
362 | { | |
363 | d->access->warning("sysfs_read_vpd: read failed: %s", strerror(errno)); | |
364 | return 0; | |
365 | } | |
366 | else if (res != len) | |
367 | return 0; | |
368 | return 1; | |
369 | } | |
370 | ||
371 | #endif /* PCI_HAVE_DO_READ */ | |
372 | ||
11f7b31b MM |
373 | static void sysfs_cleanup_dev(struct pci_dev *d) |
374 | { | |
375 | struct pci_access *a = d->access; | |
376 | ||
377 | if (a->cached_dev == d) | |
07ad085d | 378 | sysfs_flush_cache(a); |
11f7b31b MM |
379 | } |
380 | ||
381 | struct pci_methods pm_linux_sysfs = { | |
9ff67879 MM |
382 | "linux-sysfs", |
383 | "The sys filesystem on Linux", | |
11f7b31b MM |
384 | sysfs_config, |
385 | sysfs_detect, | |
386 | sysfs_init, | |
387 | sysfs_cleanup, | |
388 | sysfs_scan, | |
2849a165 | 389 | sysfs_fill_info, |
11f7b31b MM |
390 | sysfs_read, |
391 | sysfs_write, | |
9bd5b1cf | 392 | sysfs_read_vpd, |
11f7b31b MM |
393 | NULL, /* init_dev */ |
394 | sysfs_cleanup_dev | |
395 | }; |