text: /*********************************************************
   * Copyright (C) 2016-2017 VMware, Inc. All rights reserved.
   *
   * This program is free software; you can redistribute it and/or modify it
   * under the terms of the GNU Lesser General Public License as published
   * by the Free Software Foundation version 2.1 and no later version.
   *
   * This program is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   * or FITNESS FOR A PARTICULAR PURPOSE.  See the Lesser GNU General Public
   * License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public License
   * along with this program; if not, write to the Free Software Foundation, Inc.,
   * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA.
   *
   *********************************************************/
  /* Authors:
   * Thomas Hellstrom <thellstrom@vmware.com>
   */
  
  #define G_LOG_DOMAIN "resolutionCommon"
  
  #include <stdlib.h>
  #include <string.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <fcntl.h>
  #include <unistd.h>
  #include <stdio.h>
  #include "resolutionDL.h"
  #include "vmware/tools/plugin.h"
  #include "vmware/tools/utils.h"
  #include "resolutionCommon.h"
  
  #ifdef ENABLE_RESOLUTIONKMS
  
  /* The DRM device we are looking for */
  #define RESOLUTION_VENDOR     "0x15ad"
  #define RESOLUTION_DEVICE     "0x0405"
  #define RESOLUTION_KERNELNAME "vmwgfx"
  
  /* Required DRM version for resolutionKMS */
  #define RESOLUTION_DRM_MAJOR  2
  #define RESOLUTION_DRM_MINOR  12
  
  /* Required Xorg driver version for resolutionKMS default on */
  #define RESOLUTION_XORG_MAJOR 13
  #define RESOLUTION_XORG_MINOR 2
  
  /* Recognition token for Xorg driver version scanner */
  #define RESOLUTION_XORG_VERSTRING "version="
  
  /*
   * Xorg driver file names to scan for. Only the first found will be
   * scanned for version info.
   */
  static const char *driverNames[]= {
    "/usr/lib64/xorg/modules/drivers/vmware_drv.so",
    "/usr/lib/xorg/modules/drivers/vmware_drv.so"
  };
  
  static const int numDriverNames = 2;
  
  /*
   *-----------------------------------------------------------------------------
   *
   * resolutionOpenDRM --
   *
   *     Opens a file descriptor on the indicated node to the first SVGA2 device.
   *
   * Results:
   *     Returns a positive file descriptor on success. Otherwise returns -1.
   *
   * Side effects:
   *     None.
   *
   *-----------------------------------------------------------------------------
   */
  static int
  resolutionOpenDRM(const char *node) // IN: Device node base name.
  {
      struct udev *udev;
      struct udev_enumerate *enumerate;
      struct udev_list_entry *devices, *devListEntry;
      struct udev_device *dev;
      int fd = -1;
      int drmFd;
      const char *devNode = NULL;
  
      /* Force load the kernel module. */
      drmFd = drmOpen(RESOLUTION_KERNELNAME, NULL);
      if (drmFd >= 0) {
         (void) drmDropMaster(drmFd);
      }
  
      udev = udev_new();
      if (!udev) {
          goto outNoUdev;
      }
  
      /*
       * Udev error return codes that are not caught immediately are
       * typically caught in the input argument check in the udev
       * function calls following the failing call!
       */
      enumerate = udev_enumerate_new(udev);
      if (udev_enumerate_add_match_subsystem(enumerate, "drm"))
  	goto outErr;
      if (udev_enumerate_add_match_property(enumerate, "DEVTYPE", "drm_minor"))
  	goto outErr;
      if (udev_enumerate_scan_devices(enumerate))
  	goto outErr;
  
      devices = udev_enumerate_get_list_entry(enumerate);
      udev_list_entry_foreach(devListEntry, devices) {
  	const char *path, *vendor, *device;
  	struct udev_device *parent;
  
  	path = udev_list_entry_get_name(devListEntry);
  	if (!path)
  	    continue;
  	if (!strstr(path, node))
  	    continue;
  
  	dev = udev_device_new_from_syspath(udev, path);
  	if (!dev)
  	    goto outErr;
  
  	parent = udev_device_get_parent_with_subsystem_devtype(dev,
  							       "pci",
  							       NULL);
  	if (!parent)
  	    goto skipCheck;
  
  	vendor = udev_device_get_sysattr_value(parent, "vendor");
  	device = udev_device_get_sysattr_value(parent, "device");
  	if (!vendor || !device)
  	    goto skipCheck;
  
  	if (strcmp(vendor, RESOLUTION_VENDOR) ||
  	    strcmp(device, RESOLUTION_DEVICE))
  	    goto skipCheck;
  
  	devNode = udev_device_get_devnode(dev);
  	if (!devNode)
  	    goto outFound;
  
  	fd = open(devNode, O_RDWR);
  	udev_device_unref(dev);
  	break;
  
  skipCheck:
  	udev_device_unref(dev);
      }
  
      udev_enumerate_unref(enumerate);
      udev_unref(udev);
  
      if (drmFd >= 0) {
         drmClose(drmFd);
      }
  
      return fd;
  
    outFound:
      udev_device_unref(dev);
    outErr:
      udev_enumerate_unref(enumerate);
      udev_unref(udev);
    outNoUdev:
      if (drmFd >= 0) {
         drmClose(drmFd);
      }
  
      return -1;
  }
  
  /*
   *-----------------------------------------------------------------------------
   *
   * resolutionDRMCheckVersion --
   *
   *     Check that the drm version supports GUI topology communication.
   *
   *     Checks that the DRM device supports setting GUI topology from the
   *     control node, and also that the topology is communicated on the
   *     modesetting connectors.
   *
   * Results:
   *     0 if DRM device is usable for communicating GUI topology.
   *     -1 otherwise.
   *
   * Side effects:
   *     None.
   *
   *-----------------------------------------------------------------------------
   */
  static int
  resolutionDRMCheckVersion(int fd)  // IN: An open DRM file descriptor.
  {
      drmVersionPtr ver = drmGetVersion(fd);
  
      if (!ver) {
  	g_debug("%s: Failed to get DRM version.\n", __func__);
  	return -1;
      }
  
      if (ver->version_major != RESOLUTION_DRM_MAJOR ||
          ver->version_minor < RESOLUTION_DRM_MINOR) {
         g_debug("%s: Insufficient DRM version %d.%d for resolutionKMS.\n",
                 __func__, ver->version_major, ver->version_minor);
         drmFreeVersion(ver);
         return -1;
      }
  
      drmFreeVersion(ver);
      return 0;
  }
  
  
  /*
   *-----------------------------------------------------------------------------
   *
   * resolutionDRMRPrimaryCheckOpen --
   *
   *     First tries to open a render node to DRM, and if that fails opens a
   *     primary node, and drops master. Then checks that drm supports GUI
   *     topology communication.
   *
   * Results:
   *     If succesful returns a positive open file descriptor. Otherwise
   *     returns -1.
   *
   * Side effects:
   *      May temporarily become drm master if render nodes are not available,
   *      and thus race with the X server.
   *
   *-----------------------------------------------------------------------------
   */
  static int
  resolutionDRMRPrimaryCheckOpen(void)
  {
      int fd = -1;
  
      fd = resolutionOpenDRM("renderD");
      if (fd < 0) {
  	g_debug("%s: Failed to open DRM render node.\n", __func__);
  	fd = resolutionOpenDRM("card");
  	if (fd >= 0)
  	    (void) drmDropMaster(fd);
      }
      if (fd < 0) {
  	g_debug("%s: Failed to open DRM card node.\n", __func__);
  	goto outErr;
      }
  
      if (!resolutionDRMCheckVersion(fd)) {
  	return fd;
      }
  
      close(fd);
    outErr:
      return -1;
  }
  
  
  /*
   *-----------------------------------------------------------------------------
   *
   * resolutionCheckForKMS --
   *
   *     Checks whether the vmwgfx DRM is present and supports exposing
   *     layout information through connector properties and preferred modes.
   *
   * Results:
   *     If succesful returns a positive number representing an open file
   *     descriptor to the node indicated by the control argument. If
   *     unsuccessful returns -1.
   *
   * Side effects:
   *     Opens a file to DRM. The file descriptor should be closed with
   *     resolutionDRMClose() when needed.
   *
   *-----------------------------------------------------------------------------
   */
  int
  resolutionCheckForKMS(ToolsAppCtx *ctx)  // IN: The ToolsAppCtx for
  		                         // configuration db access.
  {
      GError *err = NULL;
      gboolean doResolutionKMS;
      int fd;
  
      doResolutionKMS = g_key_file_get_boolean(ctx->config, "resolutionKMS",
  					     "enable", &err);
      if (err) {
  	/*
  	 * If there is nothing in the configuration file, require
  	 * at least Xorg driver version 13.2.0, which has the autolayout
  	 * feature, to enable resolutionKMS.
  	 */
  	int major, minor, level;
  	g_clear_error(&err);
  	doResolutionKMS = FALSE;
  	if (!resolutionXorgDriverVersion(numDriverNames, driverNames,
  					 RESOLUTION_XORG_VERSTRING, &major,
  					 &minor, &level) &&
  	    (major > RESOLUTION_XORG_MAJOR ||
  	     (major == RESOLUTION_XORG_MAJOR &&
  	      minor >= RESOLUTION_XORG_MINOR))) {
  	    doResolutionKMS = TRUE;
  	    g_debug("%s: ResolutionKMS enabled based on Xorg driver version.\n",
  		    __func__);
  	} else {
  	    g_debug("%s: ResolutionKMS disabled. (No configuration).\n",
  		__func__);
  	    doResolutionKMS = FALSE;
  	}
      } else {
  	g_debug("%s: ResolutionKMS %s using configuration file info.\n",
  		__func__, (doResolutionKMS) ? "enabled" : "disabled");
      }
  
      if (!doResolutionKMS)
  	return -1;
  
      if (resolutionDLOpen()) {
  	g_warning("%s: Failed to find needed system libraries for "
  		  "resolutionKMS.\n", __func__);
  	return -1;
      } else {
  	g_message("%s: dlopen succeeded.\n", __func__);
      }
  
      fd = resolutionDRMRPrimaryCheckOpen();
  
      if (fd < 0)
  	g_warning("%s: No system support for resolutionKMS.\n", __func__);
      else
  	g_message("%s: System support available for resolutionKMS.\n",
  		  __func__);
  
      return fd;
  }
  
  /*
   *-----------------------------------------------------------------------------
   *
   * resolutionDRMClose --
   *
   *     Scans for VMWare Xorg driver files and tries to determine the Xorg
   *     driver version.
   *
   * Results:
   *     If succesful returns zero and outputs the driver version in the
   *     parameters major, minor and level. If not successful, returns -1.
   *
   * Side effects:
   *     None.
   *
   *-----------------------------------------------------------------------------
   */
  void
  resolutionDRMClose(int fd)
  {
      close(fd);
      resolutionDLClose();
  }
  
  #endif /* ENABLE_RESOLUTIONKMS */
  
  /*
   *-----------------------------------------------------------------------------
   *
   * resolutionXorgDriverVersion --
   *
   *     Scans for VMWare Xorg driver files and tries to determine the Xorg
   *     driver version.
   *
   * Results:
   *     If succesful returns zero and outputs the driver version in the
   *     parameters major, minor and level. If not successful, returns -1.
   *
   * Side effects:
   *     None.
   *
   *-----------------------------------------------------------------------------
   */
  int
  resolutionXorgDriverVersion(int numPaths,               // IN: Number of strings
  			                                // in paths.
  			    const char *paths[],        // IN: Possible driver
  			                                // paths.
  			    const char versionString[], // IN: Version token.
  			    int *major,                 // OUT: Major version #
  			    int *minor,                 // OUT: Minor version #
  			    int *level)                 // OUT: Patchlevel
                                                          // version #
  {
     FILE *driver = NULL;
     const char *curMatch;
     char curFileChar;
     int i;
  
     g_debug("%s: Scanning for VMWare Xorg drivers.\n", __func__);
     for(i = 0; i < numPaths; ++i) {
         g_debug("%s: Looking for \"%s\".\n", __func__, paths[i]);
         driver = fopen(paths[i], "r");
         if (driver)
  	   break;
     }
  
     if (!driver) {
         g_debug("%s: No driver found.\n",  __func__);
         return -1;
     }
  
     g_debug("%s: Driver found. Looking for version info.\n", __func__);
     curMatch = versionString;
     while (*curMatch) {
        if (feof(driver))
  	 goto outNotFound;
  
        curFileChar = fgetc(driver);
        if (curFileChar == *curMatch) {
  	 curMatch++;
  	 continue;
        } else if (curMatch != versionString) {
  	 curMatch = versionString;
  	 (void) ungetc(curFileChar, driver);
        }
     }
  
     if (fscanf(driver, "%d.%d.%d", major, minor, level) != 3)
        goto outNotFound;
  
     fclose(driver);
     g_debug("%s: Version info found: %d.%d.%d\n", __func__, *major, *minor,
  	   *level);
     return 0;
  
   outNotFound:
     fclose(driver);
     g_debug("%s: No version info found.\n", __func__);
     return -1;
  }
  