Merge tag 'drm-misc-next-2020-06-19' of git://anongit.freedesktop.org/drm/drm-misc...
[sfrench/cifs-2.6.git] / drivers / gpu / drm / nouveau / nouveau_display.c
index 60bef0ad028d6a1ccb86bdb9ae8d932be89110af..901ac55506d65e07be475a8220ed275653435142 100644 (file)
@@ -31,6 +31,7 @@
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_fb_helper.h>
 #include <drm/drm_fourcc.h>
+#include <drm/drm_gem_framebuffer_helper.h>
 #include <drm/drm_probe_helper.h>
 #include <drm/drm_vblank.h>
 
@@ -179,41 +180,164 @@ nouveau_display_vblank_init(struct drm_device *dev)
        return 0;
 }
 
+static const struct drm_framebuffer_funcs nouveau_framebuffer_funcs = {
+       .destroy = drm_gem_fb_destroy,
+       .create_handle = drm_gem_fb_create_handle,
+};
+
 static void
-nouveau_user_framebuffer_destroy(struct drm_framebuffer *drm_fb)
+nouveau_decode_mod(struct nouveau_drm *drm,
+                  uint64_t modifier,
+                  uint32_t *tile_mode,
+                  uint8_t *kind)
+{
+       BUG_ON(!tile_mode || !kind);
+
+       if (modifier == DRM_FORMAT_MOD_LINEAR) {
+               /* tile_mode will not be used in this case */
+               *tile_mode = 0;
+               *kind = 0;
+       } else {
+               /*
+                * Extract the block height and kind from the corresponding
+                * modifier fields.  See drm_fourcc.h for details.
+                */
+               *tile_mode = (uint32_t)(modifier & 0xF);
+               *kind = (uint8_t)((modifier >> 12) & 0xFF);
+
+               if (drm->client.device.info.chipset >= 0xc0)
+                       *tile_mode <<= 4;
+       }
+}
+
+void
+nouveau_framebuffer_get_layout(struct drm_framebuffer *fb,
+                              uint32_t *tile_mode,
+                              uint8_t *kind)
 {
-       struct nouveau_framebuffer *fb = nouveau_framebuffer(drm_fb);
+       if (fb->flags & DRM_MODE_FB_MODIFIERS) {
+               struct nouveau_drm *drm = nouveau_drm(fb->dev);
 
-       if (fb->nvbo)
-               drm_gem_object_put(&fb->nvbo->bo.base);
+               nouveau_decode_mod(drm, fb->modifier, tile_mode, kind);
+       } else {
+               const struct nouveau_bo *nvbo = nouveau_gem_object(fb->obj[0]);
 
-       drm_framebuffer_cleanup(drm_fb);
-       kfree(fb);
+               *tile_mode = nvbo->mode;
+               *kind = nvbo->kind;
+       }
 }
 
 static int
-nouveau_user_framebuffer_create_handle(struct drm_framebuffer *drm_fb,
-                                      struct drm_file *file_priv,
-                                      unsigned int *handle)
+nouveau_validate_decode_mod(struct nouveau_drm *drm,
+                           uint64_t modifier,
+                           uint32_t *tile_mode,
+                           uint8_t *kind)
 {
-       struct nouveau_framebuffer *fb = nouveau_framebuffer(drm_fb);
+       struct nouveau_display *disp = nouveau_display(drm->dev);
+       int mod;
+
+       if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA) {
+               return -EINVAL;
+       }
 
-       return drm_gem_handle_create(file_priv, &fb->nvbo->bo.base, handle);
+       BUG_ON(!disp->format_modifiers);
+
+       for (mod = 0;
+            (disp->format_modifiers[mod] != DRM_FORMAT_MOD_INVALID) &&
+            (disp->format_modifiers[mod] != modifier);
+            mod++);
+
+       if (disp->format_modifiers[mod] == DRM_FORMAT_MOD_INVALID)
+               return -EINVAL;
+
+       nouveau_decode_mod(drm, modifier, tile_mode, kind);
+
+       return 0;
 }
 
-static const struct drm_framebuffer_funcs nouveau_framebuffer_funcs = {
-       .destroy = nouveau_user_framebuffer_destroy,
-       .create_handle = nouveau_user_framebuffer_create_handle,
-};
+static inline uint32_t
+nouveau_get_width_in_blocks(uint32_t stride)
+{
+       /* GOBs per block in the x direction is always one, and GOBs are
+        * 64 bytes wide
+        */
+       static const uint32_t log_block_width = 6;
+
+       return (stride + (1 << log_block_width) - 1) >> log_block_width;
+}
+
+static inline uint32_t
+nouveau_get_height_in_blocks(struct nouveau_drm *drm,
+                            uint32_t height,
+                            uint32_t log_block_height_in_gobs)
+{
+       uint32_t log_gob_height;
+       uint32_t log_block_height;
+
+       BUG_ON(drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA);
+
+       if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI)
+               log_gob_height = 2;
+       else
+               log_gob_height = 3;
+
+       log_block_height = log_block_height_in_gobs + log_gob_height;
+
+       return (height + (1 << log_block_height) - 1) >> log_block_height;
+}
+
+static int
+nouveau_check_bl_size(struct nouveau_drm *drm, struct nouveau_bo *nvbo,
+                     uint32_t offset, uint32_t stride, uint32_t h,
+                     uint32_t tile_mode)
+{
+       uint32_t gob_size, bw, bh;
+       uint64_t bl_size;
+
+       BUG_ON(drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA);
+
+       if (drm->client.device.info.chipset >= 0xc0) {
+               if (tile_mode & 0xF)
+                       return -EINVAL;
+               tile_mode >>= 4;
+       }
+
+       if (tile_mode & 0xFFFFFFF0)
+               return -EINVAL;
+
+       if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI)
+               gob_size = 256;
+       else
+               gob_size = 512;
+
+       bw = nouveau_get_width_in_blocks(stride);
+       bh = nouveau_get_height_in_blocks(drm, h, tile_mode);
+
+       bl_size = bw * bh * (1 << tile_mode) * gob_size;
+
+       DRM_DEBUG_KMS("offset=%u stride=%u h=%u tile_mode=0x%02x bw=%u bh=%u gob_size=%u bl_size=%llu size=%lu\n",
+                     offset, stride, h, tile_mode, bw, bh, gob_size, bl_size,
+                     nvbo->bo.mem.size);
+
+       if (bl_size + offset > nvbo->bo.mem.size)
+               return -ERANGE;
+
+       return 0;
+}
 
 int
 nouveau_framebuffer_new(struct drm_device *dev,
                        const struct drm_mode_fb_cmd2 *mode_cmd,
-                       struct nouveau_bo *nvbo,
-                       struct nouveau_framebuffer **pfb)
+                       struct drm_gem_object *gem,
+                       struct drm_framebuffer **pfb)
 {
        struct nouveau_drm *drm = nouveau_drm(dev);
-       struct nouveau_framebuffer *fb;
+       struct nouveau_bo *nvbo = nouveau_gem_object(gem);
+       struct drm_framebuffer *fb;
+       const struct drm_format_info *info;
+       unsigned int width, height, i;
+       uint32_t tile_mode;
+       uint8_t kind;
        int ret;
 
         /* YUV overlays have special requirements pre-NV50 */
@@ -236,13 +360,50 @@ nouveau_framebuffer_new(struct drm_device *dev,
                return -EINVAL;
        }
 
+       if (mode_cmd->flags & DRM_MODE_FB_MODIFIERS) {
+               if (nouveau_validate_decode_mod(drm, mode_cmd->modifier[0],
+                                               &tile_mode, &kind)) {
+                       DRM_DEBUG_KMS("Unsupported modifier: 0x%llx\n",
+                                     mode_cmd->modifier[0]);
+                       return -EINVAL;
+               }
+       } else {
+               tile_mode = nvbo->mode;
+               kind = nvbo->kind;
+       }
+
+       info = drm_get_format_info(dev, mode_cmd);
+
+       for (i = 0; i < info->num_planes; i++) {
+               width = drm_format_info_plane_width(info,
+                                                   mode_cmd->width,
+                                                   i);
+               height = drm_format_info_plane_height(info,
+                                                     mode_cmd->height,
+                                                     i);
+
+               if (kind) {
+                       ret = nouveau_check_bl_size(drm, nvbo,
+                                                   mode_cmd->offsets[i],
+                                                   mode_cmd->pitches[i],
+                                                   height, tile_mode);
+                       if (ret)
+                               return ret;
+               } else {
+                       uint32_t size = mode_cmd->pitches[i] * height;
+
+                       if (size + mode_cmd->offsets[i] > nvbo->bo.mem.size)
+                               return -ERANGE;
+               }
+       }
+
        if (!(fb = *pfb = kzalloc(sizeof(*fb), GFP_KERNEL)))
                return -ENOMEM;
 
-       drm_helper_mode_fill_fb_struct(dev, &fb->base, mode_cmd);
-       fb->nvbo = nvbo;
+       drm_helper_mode_fill_fb_struct(dev, fb, mode_cmd);
+       fb->obj[0] = gem;
 
-       ret = drm_framebuffer_init(dev, &fb->base, &nouveau_framebuffer_funcs);
+       ret = drm_framebuffer_init(dev, fb, &nouveau_framebuffer_funcs);
        if (ret)
                kfree(fb);
        return ret;
@@ -253,19 +414,17 @@ nouveau_user_framebuffer_create(struct drm_device *dev,
                                struct drm_file *file_priv,
                                const struct drm_mode_fb_cmd2 *mode_cmd)
 {
-       struct nouveau_framebuffer *fb;
-       struct nouveau_bo *nvbo;
+       struct drm_framebuffer *fb;
        struct drm_gem_object *gem;
        int ret;
 
        gem = drm_gem_object_lookup(file_priv, mode_cmd->handles[0]);
        if (!gem)
                return ERR_PTR(-ENOENT);
-       nvbo = nouveau_gem_object(gem);
 
-       ret = nouveau_framebuffer_new(dev, mode_cmd, nvbo, &fb);
+       ret = nouveau_framebuffer_new(dev, mode_cmd, gem, &fb);
        if (ret == 0)
-               return &fb->base;
+               return fb;
 
        drm_gem_object_put(gem);
        return ERR_PTR(ret);
@@ -517,6 +676,7 @@ nouveau_display_create(struct drm_device *dev)
 
        dev->mode_config.preferred_depth = 24;
        dev->mode_config.prefer_shadow = 1;
+       dev->mode_config.allow_fb_modifiers = true;
 
        if (drm->client.device.info.chipset < 0x11)
                dev->mode_config.async_page_flip = false;