turnip: Move tu_bo functions to tu_drm.c
[mesa.git] / src / freedreno / vulkan / tu_drm.c
index 11c3b008155ead32859dbd92d5d9574898295f2a..99d81f33c1c9ca9fba79d36b2b41dec4c740ce46 100644 (file)
  * 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.
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  */
 
+#include "tu_private.h"
+
+#include <errno.h>
+#include <fcntl.h>
 #include <stdint.h>
 #include <sys/ioctl.h>
-#include <errno.h>
+#include <sys/mman.h>
+#include <xf86drm.h>
 
-#include <msm_drm.h>
+#include "drm-uapi/msm_drm.h"
 
-#include "tu_private.h"
+static int
+tu_drm_get_param(const struct tu_physical_device *dev,
+                 uint32_t param,
+                 uint64_t *value)
+{
+   /* Technically this requires a pipe, but the kernel only supports one pipe
+    * anyway at the time of writing and most of these are clearly pipe
+    * independent. */
+   struct drm_msm_param req = {
+      .pipe = MSM_PIPE_3D0,
+      .param = param,
+   };
+
+   int ret = drmCommandWriteRead(dev->local_fd, DRM_MSM_GET_PARAM, &req,
+                                 sizeof(req));
+   if (ret)
+      return ret;
+
+   *value = req.value;
+
+   return 0;
+}
+
+static int
+tu_drm_get_gpu_id(const struct tu_physical_device *dev, uint32_t *id)
+{
+   uint64_t value;
+   int ret = tu_drm_get_param(dev, MSM_PARAM_GPU_ID, &value);
+   if (ret)
+      return ret;
 
-#include "xf86drm.h"
+   *id = value;
+   return 0;
+}
+
+static int
+tu_drm_get_gmem_size(const struct tu_physical_device *dev, uint32_t *size)
+{
+   uint64_t value;
+   int ret = tu_drm_get_param(dev, MSM_PARAM_GMEM_SIZE, &value);
+   if (ret)
+      return ret;
+
+   *size = value;
+   return 0;
+}
+
+static int
+tu_drm_get_gmem_base(const struct tu_physical_device *dev, uint64_t *base)
+{
+   return tu_drm_get_param(dev, MSM_PARAM_GMEM_BASE, base);
+}
+
+int
+tu_drm_submitqueue_new(const struct tu_device *dev,
+                       int priority,
+                       uint32_t *queue_id)
+{
+   struct drm_msm_submitqueue req = {
+      .flags = 0,
+      .prio = priority,
+   };
+
+   int ret = drmCommandWriteRead(dev->physical_device->local_fd,
+                                 DRM_MSM_SUBMITQUEUE_NEW, &req, sizeof(req));
+   if (ret)
+      return ret;
+
+   *queue_id = req.id;
+   return 0;
+}
+
+void
+tu_drm_submitqueue_close(const struct tu_device *dev, uint32_t queue_id)
+{
+   drmCommandWrite(dev->physical_device->local_fd, DRM_MSM_SUBMITQUEUE_CLOSE,
+                   &queue_id, sizeof(uint32_t));
+}
 
 /**
  * Return gem handle on success. Return 0 on failure.
  */
-uint32_t
-tu_gem_new(struct tu_device *dev, uint64_t size, uint32_t flags)
+static uint32_t
+tu_gem_new(const struct tu_device *dev, uint64_t size, uint32_t flags)
 {
    struct drm_msm_gem_new req = {
       .size = size,
       .flags = flags,
    };
 
-
-   int ret = drmCommandWriteRead(dev->physical_device->local_fd, DRM_MSM_GEM_NEW,
-                                 &req, sizeof(req));
+   int ret = drmCommandWriteRead(dev->physical_device->local_fd,
+                                 DRM_MSM_GEM_NEW, &req, sizeof(req));
    if (ret)
       return 0;
 
    return req.handle;
 }
 
-void
-tu_gem_close(struct tu_device *dev, uint32_t gem_handle)
+static uint32_t
+tu_gem_import_dmabuf(const struct tu_device *dev, int prime_fd, uint64_t size)
+{
+   /* lseek() to get the real size */
+   off_t real_size = lseek(prime_fd, 0, SEEK_END);
+   lseek(prime_fd, 0, SEEK_SET);
+   if (real_size < 0 || (uint64_t) real_size < size)
+      return 0;
+
+   uint32_t gem_handle;
+   int ret = drmPrimeFDToHandle(dev->physical_device->local_fd, prime_fd,
+                                &gem_handle);
+   if (ret)
+      return 0;
+
+   return gem_handle;
+}
+
+static int
+tu_gem_export_dmabuf(const struct tu_device *dev, uint32_t gem_handle)
+{
+   int prime_fd;
+   int ret = drmPrimeHandleToFD(dev->physical_device->local_fd, gem_handle,
+                                DRM_CLOEXEC, &prime_fd);
+
+   return ret == 0 ? prime_fd : -1;
+}
+
+static void
+tu_gem_close(const struct tu_device *dev, uint32_t gem_handle)
 {
    struct drm_gem_close req = {
       .handle = gem_handle,
@@ -62,54 +169,279 @@ tu_gem_close(struct tu_device *dev, uint32_t gem_handle)
    drmIoctl(dev->physical_device->local_fd, DRM_IOCTL_GEM_CLOSE, &req);
 }
 
-/** Return UINT64_MAX on error. */
+/** Helper for DRM_MSM_GEM_INFO, returns 0 on error. */
 static uint64_t
-tu_gem_info(struct tu_device *dev, uint32_t gem_handle, uint32_t flags)
+tu_gem_info(const struct tu_device *dev, uint32_t gem_handle, uint32_t info)
 {
    struct drm_msm_gem_info req = {
       .handle = gem_handle,
-      .flags = flags,
+      .info = info,
    };
 
-   int ret = drmCommandWriteRead(dev->physical_device->local_fd, DRM_MSM_GEM_INFO,
-                                 &req, sizeof(req));
-   if (ret == -1)
-      return UINT64_MAX;
+   int ret = drmCommandWriteRead(dev->physical_device->local_fd,
+                                 DRM_MSM_GEM_INFO, &req, sizeof(req));
+   if (ret < 0)
+      return 0;
 
-   return req.offset;
+   return req.value;
 }
 
-/** Return UINT64_MAX on error. */
-uint64_t
-tu_gem_info_offset(struct tu_device *dev, uint32_t gem_handle)
+/** Returns the offset for CPU-side mmap of the gem handle.
+ *
+ * Returns 0 on error (an invalid mmap offset in the DRM UBI).
+ */
+static uint64_t
+tu_gem_info_offset(const struct tu_device *dev, uint32_t gem_handle)
 {
-   return tu_gem_info(dev, gem_handle, 0);
+   return tu_gem_info(dev, gem_handle, MSM_INFO_GET_OFFSET);
 }
 
-/** Return UINT64_MAX on error. */
-uint64_t
-tu_gem_info_iova(struct tu_device *dev, uint32_t gem_handle)
+/** Returns the the iova of the BO in GPU memory.
+ *
+ * Returns 0 on error (an invalid iova in the MSM DRM UABI).
+ */
+static uint64_t
+tu_gem_info_iova(const struct tu_device *dev, uint32_t gem_handle)
 {
-   return tu_gem_info(dev, gem_handle, MSM_INFO_IOVA);
+   return tu_gem_info(dev, gem_handle, MSM_INFO_GET_IOVA);
 }
 
+static VkResult
+tu_bo_init(struct tu_device *dev,
+           struct tu_bo *bo,
+           uint32_t gem_handle,
+           uint64_t size)
+{
+   uint64_t iova = tu_gem_info_iova(dev, gem_handle);
+   if (!iova)
+      return VK_ERROR_OUT_OF_DEVICE_MEMORY;
+
+   *bo = (struct tu_bo) {
+      .gem_handle = gem_handle,
+      .size = size,
+      .iova = iova,
+   };
+
+   return VK_SUCCESS;
+}
+
+VkResult
+tu_bo_init_new(struct tu_device *dev, struct tu_bo *bo, uint64_t size)
+{
+   /* TODO: Choose better flags. As of 2018-11-12, freedreno/drm/msm_bo.c
+    * always sets `flags = MSM_BO_WC`, and we copy that behavior here.
+    */
+   uint32_t gem_handle = tu_gem_new(dev, size, MSM_BO_WC);
+   if (!gem_handle)
+      return vk_error(dev->instance, VK_ERROR_OUT_OF_DEVICE_MEMORY);
+
+   VkResult result = tu_bo_init(dev, bo, gem_handle, size);
+   if (result != VK_SUCCESS) {
+      tu_gem_close(dev, gem_handle);
+      return vk_error(dev->instance, result);
+   }
+
+   return VK_SUCCESS;
+}
+
+VkResult
+tu_bo_init_dmabuf(struct tu_device *dev,
+                  struct tu_bo *bo,
+                  uint64_t size,
+                  int fd)
+{
+   uint32_t gem_handle = tu_gem_import_dmabuf(dev, fd, size);
+   if (!gem_handle)
+      return vk_error(dev->instance, VK_ERROR_INVALID_EXTERNAL_HANDLE);
+
+   VkResult result = tu_bo_init(dev, bo, gem_handle, size);
+   if (result != VK_SUCCESS) {
+      tu_gem_close(dev, gem_handle);
+      return vk_error(dev->instance, result);
+   }
+
+   return VK_SUCCESS;
+}
 
 int
-tu_drm_query_param(struct tu_physical_device *dev, uint32_t param, uint64_t *value)
+tu_bo_export_dmabuf(struct tu_device *dev, struct tu_bo *bo)
 {
-   /* Technically this requires a pipe, but the kernel only supports one pipe anyway
-    * at the time of writing and most of these are clearly pipe independent. */
-   struct drm_msm_param req = {
-      .pipe = MSM_PIPE_3D0,
-      .param = param,
-   };
+   return tu_gem_export_dmabuf(dev, bo->gem_handle);
+}
 
-   int ret = drmCommandWriteRead(dev->local_fd, DRM_MSM_GET_PARAM,
-                                 &req, sizeof(req));
-   if (ret)
-      return ret;
+VkResult
+tu_bo_map(struct tu_device *dev, struct tu_bo *bo)
+{
+   if (bo->map)
+      return VK_SUCCESS;
 
-   *value = req.value;
+   uint64_t offset = tu_gem_info_offset(dev, bo->gem_handle);
+   if (!offset)
+      return vk_error(dev->instance, VK_ERROR_OUT_OF_DEVICE_MEMORY);
 
-   return 0;
+   /* TODO: Should we use the wrapper os_mmap() like Freedreno does? */
+   void *map = mmap(0, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED,
+                    dev->physical_device->local_fd, offset);
+   if (map == MAP_FAILED)
+      return vk_error(dev->instance, VK_ERROR_MEMORY_MAP_FAILED);
+
+   bo->map = map;
+   return VK_SUCCESS;
+}
+
+void
+tu_bo_finish(struct tu_device *dev, struct tu_bo *bo)
+{
+   assert(bo->gem_handle);
+
+   if (bo->map)
+      munmap(bo->map, bo->size);
+
+   tu_gem_close(dev, bo->gem_handle);
 }
+
+static VkResult
+tu_drm_device_init(struct tu_physical_device *device,
+                   struct tu_instance *instance,
+                   drmDevicePtr drm_device)
+{
+   const char *path = drm_device->nodes[DRM_NODE_RENDER];
+   VkResult result = VK_SUCCESS;
+   drmVersionPtr version;
+   int fd;
+   int master_fd = -1;
+
+   fd = open(path, O_RDWR | O_CLOEXEC);
+   if (fd < 0) {
+      return vk_errorf(instance, VK_ERROR_INCOMPATIBLE_DRIVER,
+                       "failed to open device %s", path);
+   }
+
+   /* Version 1.3 added MSM_INFO_IOVA. */
+   const int min_version_major = 1;
+   const int min_version_minor = 3;
+
+   version = drmGetVersion(fd);
+   if (!version) {
+      close(fd);
+      return vk_errorf(instance, VK_ERROR_INCOMPATIBLE_DRIVER,
+                       "failed to query kernel driver version for device %s",
+                       path);
+   }
+
+   if (strcmp(version->name, "msm")) {
+      drmFreeVersion(version);
+      close(fd);
+      return vk_errorf(instance, VK_ERROR_INCOMPATIBLE_DRIVER,
+                       "device %s does not use the msm kernel driver", path);
+   }
+
+   if (version->version_major != min_version_major ||
+       version->version_minor < min_version_minor) {
+      result = vk_errorf(instance, VK_ERROR_INCOMPATIBLE_DRIVER,
+                         "kernel driver for device %s has version %d.%d, "
+                         "but Vulkan requires version >= %d.%d",
+                         path, version->version_major, version->version_minor,
+                         min_version_major, min_version_minor);
+      drmFreeVersion(version);
+      close(fd);
+      return result;
+   }
+
+   device->msm_major_version = version->version_major;
+   device->msm_minor_version = version->version_minor;
+
+   drmFreeVersion(version);
+
+   if (instance->debug_flags & TU_DEBUG_STARTUP)
+      tu_logi("Found compatible device '%s'.", path);
+
+   vk_object_base_init(NULL, &device->base, VK_OBJECT_TYPE_PHYSICAL_DEVICE);
+   device->instance = instance;
+   assert(strlen(path) < ARRAY_SIZE(device->path));
+   strncpy(device->path, path, ARRAY_SIZE(device->path));
+
+   if (instance->enabled_extensions.KHR_display) {
+      master_fd =
+         open(drm_device->nodes[DRM_NODE_PRIMARY], O_RDWR | O_CLOEXEC);
+      if (master_fd >= 0) {
+         /* TODO: free master_fd is accel is not working? */
+      }
+   }
+
+   device->master_fd = master_fd;
+   device->local_fd = fd;
+
+   if (tu_drm_get_gpu_id(device, &device->gpu_id)) {
+      if (instance->debug_flags & TU_DEBUG_STARTUP)
+         tu_logi("Could not query the GPU ID");
+      result = vk_errorf(instance, VK_ERROR_INITIALIZATION_FAILED,
+                         "could not get GPU ID");
+      goto fail;
+   }
+
+   if (tu_drm_get_gmem_size(device, &device->gmem_size)) {
+      if (instance->debug_flags & TU_DEBUG_STARTUP)
+         tu_logi("Could not query the GMEM size");
+      result = vk_errorf(instance, VK_ERROR_INITIALIZATION_FAILED,
+                         "could not get GMEM size");
+      goto fail;
+   }
+
+   if (tu_drm_get_gmem_base(device, &device->gmem_base)) {
+      if (instance->debug_flags & TU_DEBUG_STARTUP)
+         tu_logi("Could not query the GMEM size");
+      result = vk_errorf(instance, VK_ERROR_INITIALIZATION_FAILED,
+                         "could not get GMEM size");
+      goto fail;
+   }
+
+   return tu_physical_device_init(device, instance);
+
+fail:
+   close(fd);
+   if (master_fd != -1)
+      close(master_fd);
+   return result;
+}
+
+VkResult
+tu_enumerate_devices(struct tu_instance *instance)
+{
+   /* TODO: Check for more devices ? */
+   drmDevicePtr devices[8];
+   VkResult result = VK_ERROR_INCOMPATIBLE_DRIVER;
+   int max_devices;
+
+   instance->physical_device_count = 0;
+
+   max_devices = drmGetDevices2(0, devices, ARRAY_SIZE(devices));
+
+   if (instance->debug_flags & TU_DEBUG_STARTUP) {
+      if (max_devices < 0)
+         tu_logi("drmGetDevices2 returned error: %s\n", strerror(max_devices));
+      else
+         tu_logi("Found %d drm nodes", max_devices);
+   }
+
+   if (max_devices < 1)
+      return vk_error(instance, VK_ERROR_INCOMPATIBLE_DRIVER);
+
+   for (unsigned i = 0; i < (unsigned) max_devices; i++) {
+      if (devices[i]->available_nodes & 1 << DRM_NODE_RENDER &&
+          devices[i]->bustype == DRM_BUS_PLATFORM) {
+
+         result = tu_drm_device_init(
+            instance->physical_devices + instance->physical_device_count,
+            instance, devices[i]);
+         if (result == VK_SUCCESS)
+            ++instance->physical_device_count;
+         else if (result != VK_ERROR_INCOMPATIBLE_DRIVER)
+            break;
+      }
+   }
+   drmFreeDevices(devices, max_devices);
+
+   return result;
+}
+