1 /*
2 * Copyright © 2009 CNRS
3 * Copyright © 2009-2016 Inria. All rights reserved.
4 * Copyright © 2009-2011, 2013 Université Bordeaux
5 * Copyright © 2014 Cisco Systems, Inc. All rights reserved.
6 * Copyright © 2015 Research Organization for Information Science
7 * and Technology (RIST). All rights reserved.
8 * See COPYING in top-level directory.
9 */
10
11 #include <private/autogen/config.h>
12 #include <hwloc.h>
13 #include <hwloc/helper.h>
14 #include <hwloc/plugins.h>
15
16 /* private headers allowed for convenience because this plugin is built within hwloc */
17 #include <private/debug.h>
18 #include <private/misc.h>
19
20 #include <stdio.h>
21 #include <fcntl.h>
22 #include <string.h>
23 #include <assert.h>
24 #include <stdarg.h>
25 #ifdef HWLOC_LINUX_SYS
26 #include <dirent.h>
27 #endif
28
29 #include <pciaccess.h>
30
31 #ifndef PCI_HEADER_TYPE
32 #define PCI_HEADER_TYPE 0x0e
33 #endif
34 #ifndef PCI_HEADER_TYPE_BRIDGE
35 #define PCI_HEADER_TYPE_BRIDGE 1
36 #endif
37
38 #ifndef PCI_CLASS_DEVICE
39 #define PCI_CLASS_DEVICE 0x0a
40 #endif
41 #ifndef PCI_CLASS_BRIDGE_PCI
42 #define PCI_CLASS_BRIDGE_PCI 0x0604
43 #endif
44
45 #ifndef PCI_REVISION_ID
46 #define PCI_REVISION_ID 0x08
47 #endif
48
49 #ifndef PCI_SUBSYSTEM_VENDOR_ID
50 #define PCI_SUBSYSTEM_VENDOR_ID 0x2c
51 #endif
52 #ifndef PCI_SUBSYSTEM_ID
53 #define PCI_SUBSYSTEM_ID 0x2e
54 #endif
55
56 #ifndef PCI_PRIMARY_BUS
57 #define PCI_PRIMARY_BUS 0x18
58 #endif
59 #ifndef PCI_SECONDARY_BUS
60 #define PCI_SECONDARY_BUS 0x19
61 #endif
62 #ifndef PCI_SUBORDINATE_BUS
63 #define PCI_SUBORDINATE_BUS 0x1a
64 #endif
65
66 #ifndef PCI_CAP_ID_EXP
67 #define PCI_CAP_ID_EXP 0x10
68 #endif
69
70 #ifndef PCI_CAP_NORMAL
71 #define PCI_CAP_NORMAL 1
72 #endif
73
74 #define CONFIG_SPACE_CACHESIZE 256
75
76
77 static int
hwloc_look_pci(struct hwloc_backend * backend)78 hwloc_look_pci(struct hwloc_backend *backend)
79 {
80 struct hwloc_topology *topology = backend->topology;
81 struct hwloc_obj *first_obj = NULL, *last_obj = NULL;
82 int ret;
83 struct pci_device_iterator *iter;
84 struct pci_device *pcidev;
85 #ifdef HWLOC_LINUX_SYS
86 DIR *dir;
87 #endif
88
89 if (!(hwloc_topology_get_flags(topology) & (HWLOC_TOPOLOGY_FLAG_IO_DEVICES|HWLOC_TOPOLOGY_FLAG_WHOLE_IO)))
90 return 0;
91
92 if (hwloc_get_next_pcidev(topology, NULL)) {
93 hwloc_debug("%s", "PCI objects already added, ignoring pci backend.\n");
94 return 0;
95 }
96
97 if (!hwloc_topology_is_thissystem(topology)) {
98 hwloc_debug("%s", "\nno PCI detection (not thissystem)\n");
99 return 0;
100 }
101
102 hwloc_debug("%s", "\nScanning PCI buses...\n");
103
104 /* initialize PCI scanning */
105 ret = pci_system_init();
106 if (ret) {
107 hwloc_debug("%s", "Can not initialize libpciaccess\n");
108 return -1;
109 }
110
111 iter = pci_slot_match_iterator_create(NULL);
112
113 /* iterate over devices */
114 for (pcidev = pci_device_next(iter);
115 pcidev;
116 pcidev = pci_device_next(iter))
117 {
118 const char *vendorname, *devicename, *fullname;
119 unsigned char config_space_cache[CONFIG_SPACE_CACHESIZE];
120 struct hwloc_obj *obj;
121 unsigned os_index;
122 unsigned domain;
123 unsigned device_class;
124 unsigned short tmp16;
125 char name[128];
126 unsigned offset;
127
128 /* initialize the config space in case we fail to read it (missing permissions, etc). */
129 memset(config_space_cache, 0xff, CONFIG_SPACE_CACHESIZE);
130 pci_device_probe(pcidev);
131 pci_device_cfg_read(pcidev, config_space_cache, 0, CONFIG_SPACE_CACHESIZE, NULL);
132
133 /* try to read the domain */
134 domain = pcidev->domain;
135
136 /* try to read the device_class */
137 device_class = pcidev->device_class >> 8;
138
139 /* fixup SR-IOV buggy VF device/vendor IDs */
140 if (0xffff == pcidev->vendor_id && 0xffff == pcidev->device_id) {
141 /* SR-IOV puts ffff:ffff in Virtual Function config space.
142 * The actual VF device ID is stored at a special (dynamic) location in the Physical Function config space.
143 * VF and PF have the same vendor ID.
144 *
145 * libpciaccess just returns ffff:ffff, needs to be fixed.
146 * linuxpci is OK because sysfs files are already fixed the kernel.
147 * (pciutils is OK when it uses those Linux sysfs files.)
148 *
149 * Reading these files is an easy way to work around the libpciaccess issue on Linux,
150 * but we have no way to know if this is caused by SR-IOV or not.
151 *
152 * TODO:
153 * If PF has CAP_ID_PCIX or CAP_ID_EXP (offset>0),
154 * look for extended capability PCI_EXT_CAP_ID_SRIOV (need extended config space (more than 256 bytes)),
155 * then read the VF device ID after it (PCI_IOV_DID bytes later).
156 * Needs access to extended config space (needs root on Linux).
157 * TODO:
158 * Add string info attributes in VF and PF objects?
159 */
160 #ifdef HWLOC_LINUX_SYS
161 /* Workaround for Linux (the kernel returns the VF device/vendor IDs). */
162 char path[64];
163 char value[16];
164 FILE *file;
165 size_t read;
166
167 snprintf(path, sizeof(path), "/sys/bus/pci/devices/%04x:%02x:%02x.%01x/vendor",
168 domain, pcidev->bus, pcidev->dev, pcidev->func);
169 file = fopen(path, "r");
170 if (file) {
171 read = fread(value, 1, sizeof(value), file);
172 fclose(file);
173 if (read)
174 /* fixup the pciaccess struct so that pci_device_get_vendor_name() is correct later. */
175 pcidev->vendor_id = strtoul(value, NULL, 16);
176 }
177
178 snprintf(path, sizeof(path), "/sys/bus/pci/devices/%04x:%02x:%02x.%01x/device",
179 domain, pcidev->bus, pcidev->dev, pcidev->func);
180 file = fopen(path, "r");
181 if (file) {
182 read = fread(value, 1, sizeof(value), file);
183 fclose(file);
184 if (read)
185 /* fixup the pciaccess struct so that pci_device_get_device_name() is correct later. */
186 pcidev->device_id = strtoul(value, NULL, 16);
187 }
188 #endif
189 }
190
191 /* might be useful for debugging (note that domain might be truncated) */
192 os_index = (domain << 20) + (pcidev->bus << 12) + (pcidev->dev << 4) + pcidev->func;
193
194 obj = hwloc_alloc_setup_object(HWLOC_OBJ_PCI_DEVICE, os_index);
195 obj->attr->pcidev.domain = domain;
196 obj->attr->pcidev.bus = pcidev->bus;
197 obj->attr->pcidev.dev = pcidev->dev;
198 obj->attr->pcidev.func = pcidev->func;
199 obj->attr->pcidev.vendor_id = pcidev->vendor_id;
200 obj->attr->pcidev.device_id = pcidev->device_id;
201 obj->attr->pcidev.class_id = device_class;
202 obj->attr->pcidev.revision = config_space_cache[PCI_REVISION_ID];
203
204 obj->attr->pcidev.linkspeed = 0; /* unknown */
205 offset = hwloc_pci_find_cap(config_space_cache, PCI_CAP_ID_EXP);
206
207 if (offset > 0 && offset + 20 /* size of PCI express block up to link status */ <= CONFIG_SPACE_CACHESIZE)
208 hwloc_pci_find_linkspeed(config_space_cache, offset, &obj->attr->pcidev.linkspeed);
209
210 if (hwloc_pci_prepare_bridge(obj, config_space_cache) < 0)
211 continue;
212
213 if (obj->type == HWLOC_OBJ_PCI_DEVICE) {
214 memcpy(&tmp16, &config_space_cache[PCI_SUBSYSTEM_VENDOR_ID], sizeof(tmp16));
215 obj->attr->pcidev.subvendor_id = tmp16;
216 memcpy(&tmp16, &config_space_cache[PCI_SUBSYSTEM_ID], sizeof(tmp16));
217 obj->attr->pcidev.subdevice_id = tmp16;
218 } else {
219 /* TODO:
220 * bridge must lookup PCI_CAP_ID_SSVID and then look at offset+PCI_SSVID_VENDOR/DEVICE_ID
221 * cardbus must look at PCI_CB_SUBSYSTEM_VENDOR_ID and PCI_CB_SUBSYSTEM_ID
222 */
223 }
224
225 /* get the vendor name */
226 vendorname = pci_device_get_vendor_name(pcidev);
227 if (vendorname && *vendorname)
228 hwloc_obj_add_info(obj, "PCIVendor", vendorname);
229
230 /* get the device name */
231 devicename = pci_device_get_device_name(pcidev);
232 if (devicename && *devicename)
233 hwloc_obj_add_info(obj, "PCIDevice", devicename);
234
235 /* generate or get the fullname */
236 snprintf(name, sizeof(name), "%s%s%s",
237 vendorname ? vendorname : "",
238 vendorname && devicename ? " " : "",
239 devicename ? devicename : "");
240 fullname = name;
241 if (*name)
242 obj->name = strdup(name);
243 hwloc_debug(" %04x:%02x:%02x.%01x %04x %04x:%04x %s\n",
244 domain, pcidev->bus, pcidev->dev, pcidev->func,
245 device_class, pcidev->vendor_id, pcidev->device_id,
246 fullname && *fullname ? fullname : "??");
247
248 /* queue the object for now */
249 if (first_obj)
250 last_obj->next_sibling = obj;
251 else
252 first_obj = obj;
253 last_obj = obj;
254 }
255
256 /* finalize device scanning */
257 pci_iterator_destroy(iter);
258 pci_system_cleanup();
259
260 #ifdef HWLOC_LINUX_SYS
261 dir = opendir("/sys/bus/pci/slots/");
262 if (dir) {
263 struct dirent *dirent;
264 while ((dirent = readdir(dir)) != NULL) {
265 char path[64];
266 FILE *file;
267 if (dirent->d_name[0] == '.')
268 continue;
269 snprintf(path, sizeof(path), "/sys/bus/pci/slots/%s/address", dirent->d_name);
270 file = fopen(path, "r");
271 if (file) {
272 unsigned domain, bus, dev;
273 if (fscanf(file, "%x:%x:%x", &domain, &bus, &dev) == 3) {
274 hwloc_obj_t obj = first_obj;
275 while (obj) {
276 if (obj->attr->pcidev.domain == domain
277 && obj->attr->pcidev.bus == bus
278 && obj->attr->pcidev.dev == dev) {
279 hwloc_obj_add_info(obj, "PCISlot", dirent->d_name);
280 }
281 obj = obj->next_sibling;
282 }
283 }
284 fclose(file);
285 }
286 }
287 closedir(dir);
288 }
289 #endif
290
291 return hwloc_insert_pci_device_list(backend, first_obj);
292 }
293
294 static struct hwloc_backend *
hwloc_pci_component_instantiate(struct hwloc_disc_component * component,const void * _data1 __hwloc_attribute_unused,const void * _data2 __hwloc_attribute_unused,const void * _data3 __hwloc_attribute_unused)295 hwloc_pci_component_instantiate(struct hwloc_disc_component *component,
296 const void *_data1 __hwloc_attribute_unused,
297 const void *_data2 __hwloc_attribute_unused,
298 const void *_data3 __hwloc_attribute_unused)
299 {
300 struct hwloc_backend *backend;
301
302 /* thissystem may not be fully initialized yet, we'll check flags in discover() */
303
304 backend = hwloc_backend_alloc(component);
305 if (!backend)
306 return NULL;
307 backend->flags = HWLOC_BACKEND_FLAG_NEED_LEVELS;
308 #ifdef HWLOC_SOLARIS_SYS
309 if ((uid_t)0 != geteuid())
310 backend->discover = NULL;
311 else
312 #endif
313 backend->discover = hwloc_look_pci;
314 return backend;
315 }
316
317 static struct hwloc_disc_component hwloc_pci_disc_component = {
318 HWLOC_DISC_COMPONENT_TYPE_MISC,
319 "pci",
320 HWLOC_DISC_COMPONENT_TYPE_GLOBAL,
321 hwloc_pci_component_instantiate,
322 20,
323 NULL
324 };
325
326 static int
hwloc_pci_component_init(unsigned long flags)327 hwloc_pci_component_init(unsigned long flags)
328 {
329 if (flags)
330 return -1;
331 if (hwloc_plugin_check_namespace("pci", "hwloc_backend_alloc") < 0)
332 return -1;
333 return 0;
334 }
335
336 #ifdef HWLOC_INSIDE_PLUGIN
337 HWLOC_DECLSPEC extern const struct hwloc_component hwloc_pci_component;
338 #endif
339
340 const struct hwloc_component hwloc_pci_component = {
341 HWLOC_COMPONENT_ABI,
342 hwloc_pci_component_init, NULL,
343 HWLOC_COMPONENT_TYPE_DISC,
344 0,
345 &hwloc_pci_disc_component
346 };
347