1 /************************************************************************** 2 * 3 * Copyright 2009 VMware, Inc., Palo Alto, CA., USA 4 * All Rights Reserved. 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a 7 * copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, sub license, and/or sell copies of the Software, and to 11 * permit persons to whom the Software is furnished to do so, subject to 12 * the following conditions: 13 * 14 * The above copyright notice and this permission notice (including the 15 * next paragraph) shall be included in all copies or substantial portions 16 * of the Software. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 21 * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, 22 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 23 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 24 * USE OR OTHER DEALINGS IN THE SOFTWARE. 25 * 26 **************************************************************************/ 27 /* 28 * Thanks to krh and jcristau for the tips on 29 * going from fd to pci id via fstat and udev. 30 */ 31 32 33 #ifdef HAVE_CONFIG_H 34 #include "config.h" 35 #endif 36 #include <errno.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <xf86drm.h> 40 #include <string.h> 41 #include <unistd.h> 42 #include <sys/stat.h> 43 #include <sys/types.h> 44 #ifdef HAVE_SYS_MKDEV_H 45 #include <sys/mkdev.h> 46 #endif 47 48 #include "libdrm_macros.h" 49 #include "internal.h" 50 51 #define PATH_SIZE 512 52 53 static int 54 linux_name_from_sysfs(int fd, char **out) 55 { 56 char path[PATH_SIZE+1] = ""; /* initialize to please valgrind */ 57 char link[PATH_SIZE+1] = ""; 58 struct stat buffer; 59 unsigned maj, min; 60 char* slash_name; 61 int ret; 62 63 /* 64 * Inside the sysfs directory for the device there is a symlink 65 * to the directory representing the driver module, that path 66 * happens to hold the name of the driver. 67 * 68 * So lets get the symlink for the drm device. Then read the link 69 * and filter out the last directory which happens to be the name 70 * of the driver, which we can use to load the correct interface. 71 * 72 * Thanks to Ray Strode of Plymouth for the code. 73 */ 74 75 ret = fstat(fd, &buffer); 76 if (ret) 77 return -EINVAL; 78 79 if (!S_ISCHR(buffer.st_mode)) 80 return -EINVAL; 81 82 maj = major(buffer.st_rdev); 83 min = minor(buffer.st_rdev); 84 85 snprintf(path, PATH_SIZE, "/sys/dev/char/%d:%d/device/driver", maj, min); 86 87 if (readlink(path, link, PATH_SIZE) < 0) 88 return -EINVAL; 89 90 /* link looks something like this: ../../../bus/pci/drivers/intel */ 91 slash_name = strrchr(link, '/'); 92 if (!slash_name) 93 return -EINVAL; 94 95 /* copy name and at the same time remove the slash */ 96 *out = strdup(slash_name + 1); 97 return 0; 98 } 99 100 static int 101 linux_from_sysfs(int fd, struct kms_driver **out) 102 { 103 char *name; 104 int ret; 105 106 ret = linux_name_from_sysfs(fd, &name); 107 if (ret) 108 return ret; 109 110 #ifdef HAVE_INTEL 111 if (!strcmp(name, "intel")) 112 ret = intel_create(fd, out); 113 else 114 #endif 115 #ifdef HAVE_VMWGFX 116 if (!strcmp(name, "vmwgfx")) 117 ret = vmwgfx_create(fd, out); 118 else 119 #endif 120 #ifdef HAVE_NOUVEAU 121 if (!strcmp(name, "nouveau")) 122 ret = nouveau_create(fd, out); 123 else 124 #endif 125 #ifdef HAVE_RADEON 126 if (!strcmp(name, "radeon")) 127 ret = radeon_create(fd, out); 128 else 129 #endif 130 #ifdef HAVE_EXYNOS 131 if (!strcmp(name, "exynos")) 132 ret = exynos_create(fd, out); 133 else 134 #endif 135 ret = -ENOSYS; 136 137 free(name); 138 return ret; 139 } 140 141 #if 0 142 #define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE 143 #include <libudev.h> 144 145 struct create_record 146 { 147 unsigned vendor; 148 unsigned chip; 149 int (*func)(int fd, struct kms_driver **out); 150 }; 151 152 static const struct create_record table[] = { 153 { 0x8086, 0x2a42, intel_create }, /* i965 */ 154 #ifdef HAVE_VMWGFX 155 { 0x15ad, 0x0405, vmwgfx_create }, /* VMware vGPU */ 156 #endif 157 { 0, 0, NULL }, 158 }; 159 160 static int 161 linux_get_pciid_from_fd(int fd, unsigned *vendor_id, unsigned *chip_id) 162 { 163 struct udev *udev; 164 struct udev_device *device; 165 struct udev_device *parent; 166 const char *pci_id; 167 struct stat buffer; 168 int ret; 169 170 ret = fstat(fd, &buffer); 171 if (ret) 172 return -EINVAL; 173 174 if (!S_ISCHR(buffer.st_mode)) 175 return -EINVAL; 176 177 udev = udev_new(); 178 if (!udev) 179 return -ENOMEM; 180 181 device = udev_device_new_from_devnum(udev, 'c', buffer.st_rdev); 182 if (!device) 183 goto err_free_udev; 184 185 parent = udev_device_get_parent(device); 186 if (!parent) 187 goto err_free_device; 188 189 pci_id = udev_device_get_property_value(parent, "PCI_ID"); 190 if (!pci_id) 191 goto err_free_device; 192 193 if (sscanf(pci_id, "%x:%x", vendor_id, chip_id) != 2) 194 goto err_free_device; 195 196 udev_device_unref(device); 197 udev_unref(udev); 198 199 return 0; 200 201 err_free_device: 202 udev_device_unref(device); 203 err_free_udev: 204 udev_unref(udev); 205 return -EINVAL; 206 } 207 208 static int 209 linux_from_udev(int fd, struct kms_driver **out) 210 { 211 unsigned vendor_id, chip_id; 212 int ret, i; 213 214 ret = linux_get_pciid_from_fd(fd, &vendor_id, &chip_id); 215 if (ret) 216 return ret; 217 218 for (i = 0; table[i].func; i++) 219 if (table[i].vendor == vendor_id && table[i].chip == chip_id) 220 return table[i].func(fd, out); 221 222 return -ENOSYS; 223 } 224 #else 225 static int 226 linux_from_udev(int fd, struct kms_driver **out) 227 { 228 return -ENOSYS; 229 } 230 #endif 231 232 drm_private int 233 linux_create(int fd, struct kms_driver **out) 234 { 235 if (!dumb_create(fd, out)) 236 return 0; 237 238 if (!linux_from_udev(fd, out)) 239 return 0; 240 241 return linux_from_sysfs(fd, out); 242 } 243