modetest: close the device on exit
[platform/upstream/libdrm.git] / xf86drm.c
index 9cb1913..b49d42f 100644 (file)
--- a/xf86drm.c
+++ b/xf86drm.c
@@ -64,6 +64,7 @@
 
 #if defined(__FreeBSD__)
 #include <sys/param.h>
+#include <sys/pciio.h>
 #endif
 
 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
@@ -121,6 +122,7 @@ struct drm_pciinfo {
 static drmServerInfoPtr drm_server_info;
 
 static bool drmNodeIsDRM(int maj, int min);
+static char *drmGetMinorNameForFD(int fd, int type);
 
 drm_public void drmSetServerInfo(drmServerInfoPtr info)
 {
@@ -2775,24 +2777,16 @@ drm_public char *drmGetDeviceNameFromFd(int fd)
 {
 #ifdef __FreeBSD__
     struct stat sbuf;
-    char dname[SPECNAMELEN];
-    char name[SPECNAMELEN];
     int maj, min;
+    int nodetype;
 
     if (fstat(fd, &sbuf))
         return NULL;
 
     maj = major(sbuf.st_rdev);
     min = minor(sbuf.st_rdev);
-
-    if (!drmNodeIsDRM(maj, min) || !S_ISCHR(sbuf.st_mode))
-        return NULL;
-
-    if (!devname_r(sbuf.st_rdev, S_IFCHR, dname, sizeof(dname)))
-        return NULL;
-
-    snprintf(name, sizeof(name), "/dev/%s", dname);
-    return strdup(name);
+    nodetype = drmGetMinorType(maj, min);
+    return drmGetMinorNameForFD(fd, nodetype);
 #else
     char name[128];
     struct stat sbuf;
@@ -2946,7 +2940,7 @@ static char *drmGetMinorNameForFD(int fd, int type)
     char dname[SPECNAMELEN];
     const char *mname;
     char name[SPECNAMELEN];
-    int id, maj, min;
+    int id, maj, min, nodetype, i;
 
     if (fstat(fd, &sbuf))
         return NULL;
@@ -2964,14 +2958,25 @@ static char *drmGetMinorNameForFD(int fd, int type)
      * FreeBSD on amd64/i386/powerpc external kernel modules create node in
      * in /dev/drm/ and links in /dev/dri while a WIP in kernel driver creates
      * only device nodes in /dev/dri/ */
+
+    /* Get the node type represented by fd so we can deduce the target name */
+    nodetype = drmGetMinorType(maj, min);
+    if (nodetype == -1)
+        return (NULL);
     mname = drmGetMinorName(type);
-    if (sscanf(dname, "drm/%d", &id) != 1) {
-        snprintf(name, sizeof(name), "dri/%s", mname);
-        if (strncmp(name, dname, strlen(name)) != 0)
-            return NULL;
-        snprintf(name, sizeof(name), "/dev/%s", dname);
-    } else
-        snprintf(name, sizeof(name), DRM_DIR_NAME "/%s%d", mname, id);
+
+    for (i = 0; i < SPECNAMELEN; i++) {
+        if (isalpha(dname[i]) == 0 && dname[i] != '/')
+           break;
+    }
+    if (dname[i] == '\0')
+        return (NULL);
+
+    id = (int)strtol(&dname[i], NULL, 10);
+    id -= drmGetMinorBase(nodetype);
+    snprintf(name, sizeof(name), DRM_DIR_NAME "/%s%d", mname,
+         id + drmGetMinorBase(type));
+
     return strdup(name);
 #else
     struct stat sbuf;
@@ -3102,15 +3107,18 @@ static int drmParseSubsystemType(int maj, int min)
     int subsystem_type;
 
     snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device", maj, min);
-    if (!realpath(path, real_path))
-        return -errno;
-    snprintf(path, sizeof(path), "%s", real_path);
 
     subsystem_type = get_subsystem_type(path);
+    /* Try to get the parent (underlying) device type */
     if (subsystem_type == DRM_BUS_VIRTIO) {
+        /* Assume virtio-pci on error */
+        if (!realpath(path, real_path))
+            return DRM_BUS_VIRTIO;
         strncat(path, "/..", PATH_MAX);
         subsystem_type = get_subsystem_type(path);
-    }
+        if (subsystem_type < 0)
+            return DRM_BUS_VIRTIO;
+     }
     return subsystem_type;
 #elif defined(__OpenBSD__) || defined(__DragonFly__) || defined(__FreeBSD__)
     return DRM_BUS_PCI;
@@ -3120,6 +3128,7 @@ static int drmParseSubsystemType(int maj, int min)
 #endif
 }
 
+#ifdef __linux__
 static void
 get_pci_path(int maj, int min, char *pci_path)
 {
@@ -3135,6 +3144,7 @@ get_pci_path(int maj, int min, char *pci_path)
     if (term && strncmp(term, "/virtio", 7) == 0)
         *term = 0;
 }
+#endif
 
 #ifdef __FreeBSD__
 static int get_sysctl_pci_bus_info(int maj, int min, drmPciBusInfoPtr info)
@@ -3414,6 +3424,48 @@ static int drmParsePciDeviceInfo(int maj, int min,
     device->subdevice_id = pinfo.subdevice_id;
 
     return 0;
+#elif __FreeBSD__
+    drmPciBusInfo info;
+    struct pci_conf_io pc;
+    struct pci_match_conf patterns[1];
+    struct pci_conf results[1];
+    int fd, error;
+
+    if (get_sysctl_pci_bus_info(maj, min, &info) != 0)
+        return -EINVAL;
+
+    fd = open("/dev/pci", O_RDONLY, 0);
+    if (fd < 0)
+        return -errno;
+
+    bzero(&patterns, sizeof(patterns));
+    patterns[0].pc_sel.pc_domain = info.domain;
+    patterns[0].pc_sel.pc_bus = info.bus;
+    patterns[0].pc_sel.pc_dev = info.dev;
+    patterns[0].pc_sel.pc_func = info.func;
+    patterns[0].flags = PCI_GETCONF_MATCH_DOMAIN | PCI_GETCONF_MATCH_BUS
+                      | PCI_GETCONF_MATCH_DEV | PCI_GETCONF_MATCH_FUNC;
+    bzero(&pc, sizeof(struct pci_conf_io));
+    pc.num_patterns = 1;
+    pc.pat_buf_len = sizeof(patterns);
+    pc.patterns = patterns;
+    pc.match_buf_len = sizeof(results);
+    pc.matches = results;
+
+    if (ioctl(fd, PCIOCGETCONF, &pc) || pc.status == PCI_GETCONF_ERROR) {
+        error = errno;
+        close(fd);
+        return -error;
+    }
+    close(fd);
+
+    device->vendor_id = results[0].pc_vendor;
+    device->device_id = results[0].pc_device;
+    device->subvendor_id = results[0].pc_subvendor;
+    device->subdevice_id = results[0].pc_subdevice;
+    device->revision_id = results[0].pc_revid;
+
+    return 0;
 #else
 #warning "Missing implementation of drmParsePciDeviceInfo"
     return -EINVAL;
@@ -3563,6 +3615,46 @@ free_device:
     return ret;
 }
 
+#ifdef __linux__
+static int drm_usb_dev_path(int maj, int min, char *path, size_t len)
+{
+    char *value, *tmp_path, *slash;
+
+    snprintf(path, len, "/sys/dev/char/%d:%d/device", maj, min);
+
+    value = sysfs_uevent_get(path, "DEVTYPE");
+    if (!value)
+        return -ENOENT;
+
+    if (strcmp(value, "usb_device") == 0)
+        return 0;
+    if (strcmp(value, "usb_interface") != 0)
+        return -ENOTSUP;
+
+    /* The parent of a usb_interface is a usb_device */
+
+    tmp_path = realpath(path, NULL);
+    if (!tmp_path)
+        return -errno;
+
+    slash = strrchr(tmp_path, '/');
+    if (!slash) {
+        free(tmp_path);
+        return -EINVAL;
+    }
+
+    *slash = '\0';
+
+    if (snprintf(path, len, "%s", tmp_path) >= (int)len) {
+        free(tmp_path);
+        return -EINVAL;
+    }
+
+    free(tmp_path);
+    return 0;
+}
+#endif
+
 static int drmParseUsbBusInfo(int maj, int min, drmUsbBusInfoPtr info)
 {
 #ifdef __linux__
@@ -3570,7 +3662,9 @@ static int drmParseUsbBusInfo(int maj, int min, drmUsbBusInfoPtr info)
     unsigned int bus, dev;
     int ret;
 
-    snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device", maj, min);
+    ret = drm_usb_dev_path(maj, min, path, sizeof(path));
+    if (ret < 0)
+        return ret;
 
     value = sysfs_uevent_get(path, "BUSNUM");
     if (!value)
@@ -3609,7 +3703,9 @@ static int drmParseUsbDeviceInfo(int maj, int min, drmUsbDeviceInfoPtr info)
     unsigned int vendor, product;
     int ret;
 
-    snprintf(path, sizeof(path), "/sys/dev/char/%d:%d/device", maj, min);
+    ret = drm_usb_dev_path(maj, min, path, sizeof(path));
+    if (ret < 0)
+        return ret;
 
     value = sysfs_uevent_get(path, "PRODUCT");
     if (!value)
@@ -3875,6 +3971,7 @@ process_device(drmDevicePtr *device, const char *d_name,
 
     switch (subsystem_type) {
     case DRM_BUS_PCI:
+    case DRM_BUS_VIRTIO:
         return drmProcessPciDevice(device, node, node_type, maj, min,
                                    fetch_deviceinfo, flags);
     case DRM_BUS_USB: