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