diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/nouveau_display.c')
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_display.c | 212 |
1 files changed, 186 insertions, 26 deletions
diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c index 700817dc4fa0..496c4621cc78 100644 --- a/drivers/gpu/drm/nouveau/nouveau_display.c +++ b/drivers/gpu/drm/nouveau/nouveau_display.c @@ -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_unlocked(&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_unlocked(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; |