uterm: add PCI primary GPU detection
authorDavid Herrmann <dh.herrmann@googlemail.com>
Sat, 27 Oct 2012 11:25:44 +0000 (13:25 +0200)
committerDavid Herrmann <dh.herrmann@googlemail.com>
Sat, 27 Oct 2012 11:25:44 +0000 (13:25 +0200)
We used to simply probe every GPU that is reported by the kernel. However,
if a system has multiple GPUs that share display controllers, we cannot
use both simultaneously. Unfortunately, the kernel currently does not
notify us about this. Hence, we use some heuristics to determine which GPU
is the boot-gpu/primary-gpu.

This only adds the detection logic, it does not modify any code to use
this detection at all.

Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
Makefile.am
configure.ac
src/uterm_monitor.c
src/uterm_pci.c [new file with mode: 0644]
src/uterm_pci.h [new file with mode: 0644]

index 43d9e9d..38043fe 100644 (file)
@@ -178,6 +178,7 @@ libuterm_la_SOURCES = \
        src/uterm.h \
        src/uterm_input.h \
        src/uterm_video.h \
+       src/uterm_pci.h \
        src/uterm_video.c \
        src/uterm_monitor.c \
        src/uterm_vt.c \
@@ -205,6 +206,12 @@ libuterm_la_CPPFLAGS += $(UDEV_CFLAGS)
 libuterm_la_LIBADD += $(UDEV_LIBS)
 endif
 
+if BUILD_ENABLE_PCIACCESS
+libuterm_la_CPPFLAGS += $(PCIACCESS_CFLAGS)
+libuterm_la_LIBADD += $(PCIACCESS_LIBS)
+libuterm_la_SOURCES += src/uterm_pci.c
+endif
+
 if BUILD_ENABLE_VIDEO_FBDEV
 libuterm_la_SOURCES += src/uterm_video_fbdev.c
 endif
index ca6e4bb..ab16640 100644 (file)
@@ -103,6 +103,11 @@ PKG_CHECK_MODULES([FUSE], [fuse],
 AC_SUBST(FUSE_CFLAGS)
 AC_SUBST(FUSE_LIBS)
 
+PKG_CHECK_MODULES([PCIACCESS], [pciaccess],
+                  [have_pciaccess=yes], [have_pciaccess=no])
+AC_SUBST(PCIACCESS_CFLAGS)
+AC_SUBST(PCIACCESS_LIBS)
+
 #
 # Parse arguments
 # This parses all arguments that are given via "--enable-XY" or "--with-XY" and
@@ -200,6 +205,16 @@ if test "x$enable_hotplug" = "x" ; then
 fi
 AC_MSG_RESULT([$enable_hotplug])
 
+# pciaccess
+AC_MSG_CHECKING([whether user wants pciaccess device detection])
+AC_ARG_ENABLE([pciaccess],
+              [AS_HELP_STRING([--enable-pciaccess],
+                              [enable device detection via pciaccess])])
+if test "x$enable_pciaccess" = "x" ; then
+        enable_pciaccess="yes (default)"
+fi
+AC_MSG_RESULT([$enable_pciaccess])
+
 # eloop-dbus
 AC_MSG_CHECKING([whether user wants eloop dbus support])
 AC_ARG_ENABLE([eloop-dbus],
@@ -455,6 +470,16 @@ if test ! "x$enable_hotplug" = "xno" ; then
         fi
 fi
 
+# pciaccess
+pciaccess_avail=no
+if test ! "x$enable_pciaccess" = "xno" ; then
+        if test "x$have_pciaccess" = "xyes" ; then
+                pciaccess_avail=yes
+        elif test "x$enable_pciaccess" = "xyes" ; then
+                AC_ERROR([pciaccess libraries not found for pciaccess device detection])
+        fi
+fi
+
 # renderer bblit
 renderer_bblit_avail=no
 if test ! "x$enable_renderer_bblit" = "xno" ; then
@@ -714,6 +739,14 @@ if test "x$renderer_bblit_avail" = "xyes" ; then
         fi
 fi
 
+# pciaccess
+pciaccess_enabled=no
+if test "x$pciaccess_avail" = "xyes" ; then
+        if test "x${enable_pciaccess% *}" = "xyes" ; then
+                pciaccess_enabled=yes
+        fi
+fi
+
 # hotplug
 hotplug_enabled=no
 if test "x$hotplug_avail" = "xyes" ; then
@@ -885,6 +918,15 @@ fi
 AM_CONDITIONAL([BUILD_ENABLE_HOTPLUG],
                [test "x$hotplug_enabled" = "xyes"])
 
+# pciaccess
+if test "x$pciaccess_enabled" = "xyes" ; then
+        AC_DEFINE([BUILD_ENABLE_PCIACCESS], [1],
+                  [Use pciaccess for device detection])
+fi
+
+AM_CONDITIONAL([BUILD_ENABLE_PCIACCESS],
+               [test "x$pciaccess_enabled" = "xyes"])
+
 # renderer bblit
 if test "x$renderer_bblit_enabled" = "xyes" ; then
         AC_DEFINE([BUILD_ENABLE_RENDERER_BBLIT], [1],
@@ -1060,6 +1102,7 @@ AC_MSG_NOTICE([Build configuration:
         optimizations: $optimizations_enabled ($optimizations_avail)
            multi-seat: $multi_seat_enabled ($multi_seat_avail)
               hotplug: $hotplug_enabled ($hotplug_avail)
+            pciaccess: $pciaccess_enabled ($pciaccess_avail)
            eloop-dbus: $eloop_dbus_enabled ($eloop_dbus_avail)
 
   Video Backends:
index 2ba0565..36ce425 100644 (file)
@@ -43,6 +43,7 @@
 #include "log.h"
 #include "shl_dlist.h"
 #include "uterm.h"
+#include "uterm_pci.h"
 
 #ifdef BUILD_ENABLE_MULTI_SEAT
        #include <systemd/sd-login.h>
@@ -77,6 +78,8 @@ struct uterm_monitor {
        struct ev_fd *sd_mon_fd;
 #endif
 
+       char *pci_primary_id;
+
        struct udev *udev;
        struct udev_monitor *umon;
        struct ev_fd *umon_fd;
@@ -669,6 +672,10 @@ int uterm_monitor_new(struct uterm_monitor **out,
        mon->data = data;
        shl_dlist_init(&mon->seats);
 
+       ret = uterm_pci_get_primary_id(&mon->pci_primary_id);
+       if (ret)
+               log_warning("cannot get PCI primary ID");
+
        ret = monitor_sd_init(mon);
        if (ret)
                goto err_free;
@@ -760,6 +767,7 @@ err_udev:
 err_sd:
        monitor_sd_deinit(mon);
 err_free:
+       free(mon->pci_primary_id);
        free(mon);
        return ret;
 }
@@ -791,6 +799,7 @@ void uterm_monitor_unref(struct uterm_monitor *mon)
        udev_unref(mon->udev);
        monitor_sd_deinit(mon);
        ev_eloop_unref(mon->eloop);
+       free(mon->pci_primary_id);
        free(mon);
 }
 
diff --git a/src/uterm_pci.c b/src/uterm_pci.c
new file mode 100644 (file)
index 0000000..7bb59e1
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * uterm - Linux User-Space Terminal
+ *
+ * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * PCI Helpers
+ * This uses the pciaccess library to retrieve information from the PCI bus.
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <pciaccess.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "log.h"
+#include "uterm.h"
+#include "uterm_pci.h"
+
+#define LOG_SUBSYSTEM "pci"
+
+/* pci classes */
+#define UTERM_PCI_CLASS_PREHISTORIC            0x00
+#define UTERM_PCI_CLASS_DISPLAY                0x03
+#define UTERM_PCI_CLASS_MULTIMEDIA             0x04
+#define UTERM_PCI_CLASS_PROCESSOR              0x0b
+
+/* pci sub-classes */
+#define UTERM_PCI_SUBCLASS_DISPLAY_VGA         0x00
+#define UTERM_PCI_SUBCLASS_MULTIMEDIA_VIDEO    0x00
+#define UTERM_PCI_SUBCLASS_PROCESSOR_COPROC    0x40
+
+/* pci registers */
+#define UTERM_PCI_CMD_MEM_ENABLE               0x02
+
+static bool uterm_pci_is_gpu(struct pci_device *dev)
+{
+       uint32_t class = dev->device_class;
+       uint32_t match;
+
+       match = UTERM_PCI_CLASS_PREHISTORIC << 16;
+       if ((class & 0x00ff0000) == match)
+               return true;
+
+       match = UTERM_PCI_CLASS_DISPLAY << 16;
+       if ((class & 0x00ff0000) == match)
+               return true;
+
+       match = UTERM_PCI_CLASS_MULTIMEDIA << 16;
+       match |= UTERM_PCI_SUBCLASS_MULTIMEDIA_VIDEO << 8;
+       if ((class & 0x00ffff00) == match)
+               return true;
+
+       match = UTERM_PCI_CLASS_PROCESSOR << 16;
+       match |= UTERM_PCI_SUBCLASS_PROCESSOR_COPROC << 8;
+       if ((class & 0x00ffff00) == match)
+               return true;
+
+       return false;
+}
+
+static bool uterm_pci_is_vga(struct pci_device *dev)
+{
+       uint32_t class = dev->device_class;
+       uint32_t match;
+
+       match = UTERM_PCI_CLASS_DISPLAY << 16;
+       match |= UTERM_PCI_SUBCLASS_DISPLAY_VGA << 8;
+       return (class & 0x00ffff00) == match;
+}
+
+static const struct pci_slot_match uterm_pci_match = {
+       .domain = PCI_MATCH_ANY,
+       .bus = PCI_MATCH_ANY,
+       .dev = PCI_MATCH_ANY,
+       .func = PCI_MATCH_ANY,
+       .match_data = 0,
+};
+
+#define UTERM_PCI_FORMAT "pci:%04x:%02x:%02x.%d"
+
+int uterm_pci_get_primary_id(char **out)
+{
+       int ret;
+       struct pci_device_iterator *iter;
+       struct pci_device *dev;
+       char *buf;
+       uint16_t cmd;
+       unsigned int num;
+
+       if (!out)
+               return -EINVAL;
+
+       ret = pci_system_init();
+       if (ret) {
+               log_error("cannot initialize pciaccess library (%d/%d): %m",
+                         ret, errno);
+               return -EFAULT;
+       }
+
+       iter = pci_slot_match_iterator_create(&uterm_pci_match);
+       if (!iter) {
+               log_error("cannot create pci-slot iterator (%d): %m",
+                         errno);
+               ret = -EFAULT;
+               goto out_cleanup;
+       }
+
+       buf = NULL;
+       num = 0;
+       while ((dev = pci_device_next(iter))) {
+               if (!uterm_pci_is_gpu(dev))
+                       continue;
+
+               ++num;
+               if (!pci_device_is_boot_vga(dev))
+                       continue;
+
+               log_debug("primary PCI GPU: " UTERM_PCI_FORMAT,
+                         dev->domain, dev->bus, dev->dev, dev->func);
+
+               if (buf) {
+                       log_warning("multiple primary PCI GPUs found");
+                       continue;
+               }
+
+               ret = asprintf(&buf, UTERM_PCI_FORMAT, dev->domain, dev->bus,
+                              dev->dev, dev->func);
+               if (ret < 0) {
+                       log_error("cannot allocate memory for PCI name");
+                       goto out_iter;
+               }
+       }
+
+       free(iter);
+       if (buf) {
+               ret = 0;
+               *out = buf;
+               goto out_cleanup;
+       }
+
+       /* If no GPU is marked as boot_vga, we try finding a VGA card */
+       iter = pci_slot_match_iterator_create(&uterm_pci_match);
+       if (!iter) {
+               log_error("cannot create pci-slot iterator (%d): %m",
+                         errno);
+               ret = -EFAULT;
+               goto out_cleanup;
+       }
+
+       while ((dev = pci_device_next(iter))) {
+               if (!uterm_pci_is_gpu(dev))
+                       continue;
+
+               ret = pci_device_cfg_read_u16(dev, &cmd, 4);
+               if (ret)
+                       continue;
+               if (!(cmd & UTERM_PCI_CMD_MEM_ENABLE))
+                       continue;
+               if (num != 1 && !uterm_pci_is_vga(dev))
+                       continue;
+
+               log_debug("primary PCI VGA GPU: " UTERM_PCI_FORMAT,
+                         dev->domain, dev->bus, dev->dev, dev->func);
+
+               if (buf) {
+                       log_warning("multiple primary PCI VGA GPUs found");
+                       continue;
+               }
+
+               ret = asprintf(&buf, UTERM_PCI_FORMAT, dev->domain, dev->bus,
+                              dev->dev, dev->func);
+               if (ret < 0) {
+                       log_error("cannot allocate memory for PCI name");
+                       goto out_iter;
+               }
+       }
+
+       if (buf) {
+               ret = 0;
+               *out = buf;
+       } else {
+               log_warning("no primary PCI GPU found");
+               ret = -ENOENT;
+       }
+
+out_iter:
+       free(iter);
+out_cleanup:
+       pci_system_cleanup();
+       return ret;
+}
diff --git a/src/uterm_pci.h b/src/uterm_pci.h
new file mode 100644 (file)
index 0000000..dbb28e2
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * uterm - Linux User-Space Terminal
+ *
+ * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * PCI Helpers
+ * This uses the pciaccess library to retrieve information from the PCI bus.
+ */
+
+#ifndef UTERM_PCI_H
+#define UTERM_PCI_H
+
+#include <stdlib.h>
+#include "uterm.h"
+
+#ifdef BUILD_ENABLE_PCIACCESS
+
+int uterm_pci_get_primary_id(char **out);
+
+#else
+
+static inline int uterm_pci_get_primary_id(char **out)
+{
+       return -EOPNOTSUPP;
+}
+
+#endif
+
+#endif /* UTERM_PCI_H */