2016-08-15 17:07:02 +03:00
/*
* Copyright ( c ) 2016 Intel Corporation
*
* Permission to use , copy , modify , distribute , and sell this software and its
* documentation for any purpose is hereby granted without fee , provided that
* the above copyright notice appear in all copies and that both that copyright
* notice and this permission notice appear in supporting documentation , and
* that the name of the copyright holders not be used in advertising or
* publicity pertaining to distribution of the software without specific ,
* written prior permission . The copyright holders make no representations
* about the suitability of this software for any purpose . It is provided " as
* is " without express or implied warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE ,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS , IN NO
* EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL , INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE ,
* DATA OR PROFITS , WHETHER IN AN ACTION OF CONTRACT , NEGLIGENCE OR OTHER
* TORTIOUS ACTION , ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
* OF THIS SOFTWARE .
*/
# include <linux/export.h>
# include <drm/drmP.h>
# include <drm/drm_auth.h>
# include <drm/drm_framebuffer.h>
2017-04-03 11:33:03 +03:00
# include <drm/drm_atomic.h>
2017-11-07 22:13:40 +03:00
# include <drm/drm_print.h>
2016-08-15 17:07:02 +03:00
2017-11-07 22:13:40 +03:00
# include "drm_internal.h"
2016-08-15 17:07:02 +03:00
# include "drm_crtc_internal.h"
2016-08-12 23:48:48 +03:00
/**
* DOC : overview
*
* Frame buffers are abstract memory objects that provide a source of pixels to
* scanout to a CRTC . Applications explicitly request the creation of frame
* buffers through the DRM_IOCTL_MODE_ADDFB ( 2 ) ioctls and receive an opaque
* handle that can be passed to the KMS CRTC control , plane configuration and
* page flip functions .
*
* Frame buffers rely on the underlying memory manager for allocating backing
* storage . When creating a frame buffer applications pass a memory handle
* ( or a list of memory handles for multi - planar formats ) through the
2016-12-29 23:48:26 +03:00
* & struct drm_mode_fb_cmd2 argument . For drivers using GEM as their userspace
2016-08-12 23:48:48 +03:00
* buffer management interface this would be a GEM handle . Drivers are however
* free to use their own backing storage object handles , e . g . vmwgfx directly
* exposes special TTM handles to userspace and so expects TTM handles in the
* create ioctl and not GEM handles .
*
2016-12-29 23:48:26 +03:00
* Framebuffers are tracked with & struct drm_framebuffer . They are published
2016-08-12 23:48:48 +03:00
* using drm_framebuffer_init ( ) - after calling that function userspace can use
* and access the framebuffer object . The helper function
* drm_helper_mode_fill_fb_struct ( ) can be used to pre - fill the required
* metadata fields .
*
* The lifetime of a drm framebuffer is controlled with a reference count ,
2017-02-28 17:46:40 +03:00
* drivers can grab additional references with drm_framebuffer_get ( ) and drop
* them again with drm_framebuffer_put ( ) . For driver - private framebuffers for
* which the last reference is never dropped ( e . g . for the fbdev framebuffer
* when the struct & struct drm_framebuffer is embedded into the fbdev helper
* struct ) drivers can manually clean up a framebuffer at module unload time
* with drm_framebuffer_unregister_private ( ) . But doing this is not
* recommended , and it ' s better to have a normal free - standing & struct
2017-01-25 09:26:45 +03:00
* drm_framebuffer .
2016-08-12 23:48:48 +03:00
*/
2016-09-21 11:59:24 +03:00
int drm_framebuffer_check_src_coords ( uint32_t src_x , uint32_t src_y ,
uint32_t src_w , uint32_t src_h ,
const struct drm_framebuffer * fb )
{
unsigned int fb_width , fb_height ;
fb_width = fb - > width < < 16 ;
fb_height = fb - > height < < 16 ;
/* Make sure source coordinates are inside the fb. */
if ( src_w > fb_width | |
src_x > fb_width - src_w | |
src_h > fb_height | |
src_y > fb_height - src_h ) {
DRM_DEBUG_KMS ( " Invalid source coordinates "
2017-11-01 21:35:33 +03:00
" %u.%06ux%u.%06u+%u.%06u+%u.%06u (fb %ux%u) \n " ,
2016-09-21 11:59:24 +03:00
src_w > > 16 , ( ( src_w & 0xffff ) * 15625 ) > > 10 ,
src_h > > 16 , ( ( src_h & 0xffff ) * 15625 ) > > 10 ,
src_x > > 16 , ( ( src_x & 0xffff ) * 15625 ) > > 10 ,
2017-11-01 21:35:33 +03:00
src_y > > 16 , ( ( src_y & 0xffff ) * 15625 ) > > 10 ,
fb - > width , fb - > height ) ;
2016-09-21 11:59:24 +03:00
return - ENOSPC ;
}
return 0 ;
}
2016-08-15 17:07:02 +03:00
/**
* drm_mode_addfb - add an FB to the graphics configuration
* @ dev : drm device for the ioctl
* @ data : data pointer for the ioctl
* @ file_priv : drm file for the ioctl call
*
* Add a new FB to the specified CRTC , given a user request . This is the
* original addfb ioctl which only supported RGB formats .
*
* Called by the user via ioctl .
*
* Returns :
* Zero on success , negative errno on failure .
*/
int drm_mode_addfb ( struct drm_device * dev ,
void * data , struct drm_file * file_priv )
{
struct drm_mode_fb_cmd * or = data ;
struct drm_mode_fb_cmd2 r = { } ;
int ret ;
/* convert to new format and call new ioctl */
r . fb_id = or - > fb_id ;
r . width = or - > width ;
r . height = or - > height ;
r . pitches [ 0 ] = or - > pitch ;
r . pixel_format = drm_mode_legacy_fb_format ( or - > bpp , or - > depth ) ;
r . handles [ 0 ] = or - > handle ;
2018-02-03 22:11:23 +03:00
if ( r . pixel_format = = DRM_FORMAT_XRGB2101010 & &
dev - > driver - > driver_features & DRIVER_PREFER_XBGR_30BPP )
r . pixel_format = DRM_FORMAT_XBGR2101010 ;
2016-08-15 17:07:02 +03:00
ret = drm_mode_addfb2 ( dev , & r , file_priv ) ;
if ( ret )
return ret ;
or - > fb_id = r . fb_id ;
return 0 ;
}
2017-03-21 21:12:14 +03:00
static int fb_plane_width ( int width ,
const struct drm_format_info * format , int plane )
{
if ( plane = = 0 )
return width ;
2017-03-21 21:12:15 +03:00
return DIV_ROUND_UP ( width , format - > hsub ) ;
2017-03-21 21:12:14 +03:00
}
static int fb_plane_height ( int height ,
const struct drm_format_info * format , int plane )
{
if ( plane = = 0 )
return height ;
2017-03-21 21:12:15 +03:00
return DIV_ROUND_UP ( height , format - > vsub ) ;
2017-03-21 21:12:14 +03:00
}
2017-03-21 21:12:16 +03:00
static int framebuffer_check ( struct drm_device * dev ,
const struct drm_mode_fb_cmd2 * r )
2016-08-15 17:07:02 +03:00
{
2016-10-18 01:41:11 +03:00
const struct drm_format_info * info ;
int i ;
2016-08-15 17:07:02 +03:00
2017-03-21 21:12:16 +03:00
/* check if the format is supported at all */
2016-10-18 01:41:12 +03:00
info = __drm_format_info ( r - > pixel_format & ~ DRM_FORMAT_BIG_ENDIAN ) ;
2016-10-18 01:41:11 +03:00
if ( ! info ) {
2016-11-12 04:12:56 +03:00
struct drm_format_name_buf format_name ;
DRM_DEBUG_KMS ( " bad framebuffer format %s \n " ,
drm_get_format_name ( r - > pixel_format ,
& format_name ) ) ;
2016-10-18 01:41:11 +03:00
return - EINVAL ;
2016-08-15 17:07:02 +03:00
}
2017-03-21 21:12:16 +03:00
/* now let the driver pick its own format info */
info = drm_get_format_info ( dev , r ) ;
2017-03-21 21:12:15 +03:00
if ( r - > width = = 0 ) {
2016-08-15 17:07:02 +03:00
DRM_DEBUG_KMS ( " bad framebuffer width %u \n " , r - > width ) ;
return - EINVAL ;
}
2017-03-21 21:12:15 +03:00
if ( r - > height = = 0 ) {
2016-08-15 17:07:02 +03:00
DRM_DEBUG_KMS ( " bad framebuffer height %u \n " , r - > height ) ;
return - EINVAL ;
}
2016-10-18 01:41:11 +03:00
for ( i = 0 ; i < info - > num_planes ; i + + ) {
2017-03-21 21:12:14 +03:00
unsigned int width = fb_plane_width ( r - > width , info , i ) ;
unsigned int height = fb_plane_height ( r - > height , info , i ) ;
2016-10-18 01:41:11 +03:00
unsigned int cpp = info - > cpp [ i ] ;
2016-08-15 17:07:02 +03:00
if ( ! r - > handles [ i ] ) {
DRM_DEBUG_KMS ( " no buffer object handle for plane %d \n " , i ) ;
return - EINVAL ;
}
if ( ( uint64_t ) width * cpp > UINT_MAX )
return - ERANGE ;
if ( ( uint64_t ) height * r - > pitches [ i ] + r - > offsets [ i ] > UINT_MAX )
return - ERANGE ;
if ( r - > pitches [ i ] < width * cpp ) {
DRM_DEBUG_KMS ( " bad pitch %u for plane %d \n " , r - > pitches [ i ] , i ) ;
return - EINVAL ;
}
if ( r - > modifier [ i ] & & ! ( r - > flags & DRM_MODE_FB_MODIFIERS ) ) {
DRM_DEBUG_KMS ( " bad fb modifier %llu for plane %d \n " ,
r - > modifier [ i ] , i ) ;
return - EINVAL ;
}
2016-11-16 14:33:16 +03:00
if ( r - > flags & DRM_MODE_FB_MODIFIERS & &
r - > modifier [ i ] ! = r - > modifier [ 0 ] ) {
DRM_DEBUG_KMS ( " bad fb modifier %llu for plane %d \n " ,
r - > modifier [ i ] , i ) ;
return - EINVAL ;
}
2016-08-15 17:07:02 +03:00
/* modifier specific checks: */
switch ( r - > modifier [ i ] ) {
case DRM_FORMAT_MOD_SAMSUNG_64_32_TILE :
/* NOTE: the pitch restriction may be lifted later if it turns
* out that no hw has this restriction :
*/
if ( r - > pixel_format ! = DRM_FORMAT_NV12 | |
width % 128 | | height % 32 | |
r - > pitches [ i ] % 128 ) {
DRM_DEBUG_KMS ( " bad modifier data for plane %d \n " , i ) ;
return - EINVAL ;
}
break ;
default :
break ;
}
}
2016-10-18 01:41:11 +03:00
for ( i = info - > num_planes ; i < 4 ; i + + ) {
2016-08-15 17:07:02 +03:00
if ( r - > modifier [ i ] ) {
DRM_DEBUG_KMS ( " non-zero modifier for unused plane %d \n " , i ) ;
return - EINVAL ;
}
/* Pre-FB_MODIFIERS userspace didn't clear the structs properly. */
if ( ! ( r - > flags & DRM_MODE_FB_MODIFIERS ) )
continue ;
if ( r - > handles [ i ] ) {
DRM_DEBUG_KMS ( " buffer object handle for unused plane %d \n " , i ) ;
return - EINVAL ;
}
if ( r - > pitches [ i ] ) {
DRM_DEBUG_KMS ( " non-zero pitch for unused plane %d \n " , i ) ;
return - EINVAL ;
}
if ( r - > offsets [ i ] ) {
DRM_DEBUG_KMS ( " non-zero offset for unused plane %d \n " , i ) ;
return - EINVAL ;
}
}
return 0 ;
}
struct drm_framebuffer *
drm_internal_framebuffer_create ( struct drm_device * dev ,
const struct drm_mode_fb_cmd2 * r ,
struct drm_file * file_priv )
{
struct drm_mode_config * config = & dev - > mode_config ;
struct drm_framebuffer * fb ;
int ret ;
if ( r - > flags & ~ ( DRM_MODE_FB_INTERLACED | DRM_MODE_FB_MODIFIERS ) ) {
DRM_DEBUG_KMS ( " bad framebuffer flags 0x%08x \n " , r - > flags ) ;
return ERR_PTR ( - EINVAL ) ;
}
if ( ( config - > min_width > r - > width ) | | ( r - > width > config - > max_width ) ) {
DRM_DEBUG_KMS ( " bad framebuffer width %d, should be >= %d && <= %d \n " ,
r - > width , config - > min_width , config - > max_width ) ;
return ERR_PTR ( - EINVAL ) ;
}
if ( ( config - > min_height > r - > height ) | | ( r - > height > config - > max_height ) ) {
DRM_DEBUG_KMS ( " bad framebuffer height %d, should be >= %d && <= %d \n " ,
r - > height , config - > min_height , config - > max_height ) ;
return ERR_PTR ( - EINVAL ) ;
}
if ( r - > flags & DRM_MODE_FB_MODIFIERS & &
! dev - > mode_config . allow_fb_modifiers ) {
DRM_DEBUG_KMS ( " driver does not support fb modifiers \n " ) ;
return ERR_PTR ( - EINVAL ) ;
}
2017-03-21 21:12:16 +03:00
ret = framebuffer_check ( dev , r ) ;
2016-08-15 17:07:02 +03:00
if ( ret )
return ERR_PTR ( ret ) ;
fb = dev - > mode_config . funcs - > fb_create ( dev , file_priv , r ) ;
if ( IS_ERR ( fb ) ) {
DRM_DEBUG_KMS ( " could not create framebuffer \n " ) ;
return fb ;
}
return fb ;
}
/**
* drm_mode_addfb2 - add an FB to the graphics configuration
* @ dev : drm device for the ioctl
* @ data : data pointer for the ioctl
* @ file_priv : drm file for the ioctl call
*
* Add a new FB to the specified CRTC , given a user request with format . This is
* the 2 nd version of the addfb ioctl , which supports multi - planar framebuffers
* and uses fourcc codes as pixel format specifiers .
*
* Called by the user via ioctl .
*
* Returns :
* Zero on success , negative errno on failure .
*/
int drm_mode_addfb2 ( struct drm_device * dev ,
void * data , struct drm_file * file_priv )
{
struct drm_mode_fb_cmd2 * r = data ;
struct drm_framebuffer * fb ;
if ( ! drm_core_check_feature ( dev , DRIVER_MODESET ) )
return - EINVAL ;
fb = drm_internal_framebuffer_create ( dev , r , file_priv ) ;
if ( IS_ERR ( fb ) )
return PTR_ERR ( fb ) ;
DRM_DEBUG_KMS ( " [FB:%d] \n " , fb - > base . id ) ;
r - > fb_id = fb - > base . id ;
/* Transfer ownership to the filp for reaping on close */
mutex_lock ( & file_priv - > fbs_lock ) ;
list_add ( & fb - > filp_head , & file_priv - > fbs ) ;
mutex_unlock ( & file_priv - > fbs_lock ) ;
return 0 ;
}
struct drm_mode_rmfb_work {
struct work_struct work ;
struct list_head fbs ;
} ;
static void drm_mode_rmfb_work_fn ( struct work_struct * w )
{
struct drm_mode_rmfb_work * arg = container_of ( w , typeof ( * arg ) , work ) ;
while ( ! list_empty ( & arg - > fbs ) ) {
struct drm_framebuffer * fb =
list_first_entry ( & arg - > fbs , typeof ( * fb ) , filp_head ) ;
list_del_init ( & fb - > filp_head ) ;
drm_framebuffer_remove ( fb ) ;
}
}
/**
* drm_mode_rmfb - remove an FB from the configuration
* @ dev : drm device for the ioctl
* @ data : data pointer for the ioctl
* @ file_priv : drm file for the ioctl call
*
* Remove the FB specified by the user .
*
* Called by the user via ioctl .
*
* Returns :
* Zero on success , negative errno on failure .
*/
int drm_mode_rmfb ( struct drm_device * dev ,
void * data , struct drm_file * file_priv )
{
struct drm_framebuffer * fb = NULL ;
struct drm_framebuffer * fbl = NULL ;
uint32_t * id = data ;
int found = 0 ;
if ( ! drm_core_check_feature ( dev , DRIVER_MODESET ) )
return - EINVAL ;
2017-03-15 09:25:07 +03:00
fb = drm_framebuffer_lookup ( dev , file_priv , * id ) ;
2016-08-15 17:07:02 +03:00
if ( ! fb )
return - ENOENT ;
mutex_lock ( & file_priv - > fbs_lock ) ;
list_for_each_entry ( fbl , & file_priv - > fbs , filp_head )
if ( fb = = fbl )
found = 1 ;
if ( ! found ) {
mutex_unlock ( & file_priv - > fbs_lock ) ;
goto fail_unref ;
}
list_del_init ( & fb - > filp_head ) ;
mutex_unlock ( & file_priv - > fbs_lock ) ;
/* drop the reference we picked up in framebuffer lookup */
2017-02-28 17:46:40 +03:00
drm_framebuffer_put ( fb ) ;
2016-08-15 17:07:02 +03:00
/*
* we now own the reference that was stored in the fbs list
*
* drm_framebuffer_remove may fail with - EINTR on pending signals ,
* so run this in a separate stack as there ' s no way to correctly
* handle this after the fb is already removed from the lookup table .
*/
if ( drm_framebuffer_read_refcount ( fb ) > 1 ) {
struct drm_mode_rmfb_work arg ;
INIT_WORK_ONSTACK ( & arg . work , drm_mode_rmfb_work_fn ) ;
INIT_LIST_HEAD ( & arg . fbs ) ;
list_add_tail ( & fb - > filp_head , & arg . fbs ) ;
schedule_work ( & arg . work ) ;
flush_work ( & arg . work ) ;
destroy_work_on_stack ( & arg . work ) ;
} else
2017-02-28 17:46:40 +03:00
drm_framebuffer_put ( fb ) ;
2016-08-15 17:07:02 +03:00
return 0 ;
fail_unref :
2017-02-28 17:46:40 +03:00
drm_framebuffer_put ( fb ) ;
2016-08-15 17:07:02 +03:00
return - ENOENT ;
}
/**
* drm_mode_getfb - get FB info
* @ dev : drm device for the ioctl
* @ data : data pointer for the ioctl
* @ file_priv : drm file for the ioctl call
*
* Lookup the FB given its ID and return info about it .
*
* Called by the user via ioctl .
*
* Returns :
* Zero on success , negative errno on failure .
*/
int drm_mode_getfb ( struct drm_device * dev ,
void * data , struct drm_file * file_priv )
{
struct drm_mode_fb_cmd * r = data ;
struct drm_framebuffer * fb ;
int ret ;
if ( ! drm_core_check_feature ( dev , DRIVER_MODESET ) )
return - EINVAL ;
2017-03-15 09:25:07 +03:00
fb = drm_framebuffer_lookup ( dev , file_priv , r - > fb_id ) ;
2016-08-15 17:07:02 +03:00
if ( ! fb )
return - ENOENT ;
r - > height = fb - > height ;
r - > width = fb - > width ;
2016-12-15 00:31:35 +03:00
r - > depth = fb - > format - > depth ;
2016-12-15 00:32:20 +03:00
r - > bpp = fb - > format - > cpp [ 0 ] * 8 ;
2016-08-15 17:07:02 +03:00
r - > pitch = fb - > pitches [ 0 ] ;
if ( fb - > funcs - > create_handle ) {
if ( drm_is_current_master ( file_priv ) | | capable ( CAP_SYS_ADMIN ) | |
drm_is_control_client ( file_priv ) ) {
ret = fb - > funcs - > create_handle ( fb , file_priv ,
& r - > handle ) ;
} else {
/* GET_FB() is an unprivileged ioctl so we must not
* return a buffer - handle to non - master processes ! For
* backwards - compatibility reasons , we cannot make
* GET_FB ( ) privileged , so just return an invalid handle
* for non - masters . */
r - > handle = 0 ;
ret = 0 ;
}
} else {
ret = - ENODEV ;
}
2017-02-28 17:46:40 +03:00
drm_framebuffer_put ( fb ) ;
2016-08-15 17:07:02 +03:00
return ret ;
}
/**
* drm_mode_dirtyfb_ioctl - flush frontbuffer rendering on an FB
* @ dev : drm device for the ioctl
* @ data : data pointer for the ioctl
* @ file_priv : drm file for the ioctl call
*
* Lookup the FB and flush out the damaged area supplied by userspace as a clip
* rectangle list . Generic userspace which does frontbuffer rendering must call
* this ioctl to flush out the changes on manual - update display outputs , e . g .
* usb display - link , mipi manual update panels or edp panel self refresh modes .
*
* Modesetting drivers which always update the frontbuffer do not need to
2017-01-25 09:26:45 +03:00
* implement the corresponding & drm_framebuffer_funcs . dirty callback .
2016-08-15 17:07:02 +03:00
*
* Called by the user via ioctl .
*
* Returns :
* Zero on success , negative errno on failure .
*/
int drm_mode_dirtyfb_ioctl ( struct drm_device * dev ,
void * data , struct drm_file * file_priv )
{
struct drm_clip_rect __user * clips_ptr ;
struct drm_clip_rect * clips = NULL ;
struct drm_mode_fb_dirty_cmd * r = data ;
struct drm_framebuffer * fb ;
unsigned flags ;
int num_clips ;
int ret ;
if ( ! drm_core_check_feature ( dev , DRIVER_MODESET ) )
return - EINVAL ;
2017-03-15 09:25:07 +03:00
fb = drm_framebuffer_lookup ( dev , file_priv , r - > fb_id ) ;
2016-08-15 17:07:02 +03:00
if ( ! fb )
return - ENOENT ;
num_clips = r - > num_clips ;
clips_ptr = ( struct drm_clip_rect __user * ) ( unsigned long ) r - > clips_ptr ;
if ( ! num_clips ! = ! clips_ptr ) {
ret = - EINVAL ;
goto out_err1 ;
}
flags = DRM_MODE_FB_DIRTY_FLAGS & r - > flags ;
/* If userspace annotates copy, clips must come in pairs */
if ( flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY & & ( num_clips % 2 ) ) {
ret = - EINVAL ;
goto out_err1 ;
}
if ( num_clips & & clips_ptr ) {
if ( num_clips < 0 | | num_clips > DRM_MODE_FB_DIRTY_MAX_CLIPS ) {
ret = - EINVAL ;
goto out_err1 ;
}
clips = kcalloc ( num_clips , sizeof ( * clips ) , GFP_KERNEL ) ;
if ( ! clips ) {
ret = - ENOMEM ;
goto out_err1 ;
}
ret = copy_from_user ( clips , clips_ptr ,
num_clips * sizeof ( * clips ) ) ;
if ( ret ) {
ret = - EFAULT ;
goto out_err2 ;
}
}
if ( fb - > funcs - > dirty ) {
ret = fb - > funcs - > dirty ( fb , file_priv , flags , r - > color ,
clips , num_clips ) ;
} else {
ret = - ENOSYS ;
}
out_err2 :
kfree ( clips ) ;
out_err1 :
2017-02-28 17:46:40 +03:00
drm_framebuffer_put ( fb ) ;
2016-08-15 17:07:02 +03:00
return ret ;
}
/**
* drm_fb_release - remove and free the FBs on this file
* @ priv : drm file for the ioctl
*
* Destroy all the FBs associated with @ filp .
*
* Called by the user via ioctl .
*
* Returns :
* Zero on success , negative errno on failure .
*/
void drm_fb_release ( struct drm_file * priv )
{
struct drm_framebuffer * fb , * tfb ;
struct drm_mode_rmfb_work arg ;
INIT_LIST_HEAD ( & arg . fbs ) ;
/*
* When the file gets released that means no one else can access the fb
* list any more , so no need to grab fpriv - > fbs_lock . And we need to
* avoid upsetting lockdep since the universal cursor code adds a
* framebuffer while holding mutex locks .
*
* Note that a real deadlock between fpriv - > fbs_lock and the modeset
* locks is impossible here since no one else but this function can get
* at it any more .
*/
list_for_each_entry_safe ( fb , tfb , & priv - > fbs , filp_head ) {
if ( drm_framebuffer_read_refcount ( fb ) > 1 ) {
list_move_tail ( & fb - > filp_head , & arg . fbs ) ;
} else {
list_del_init ( & fb - > filp_head ) ;
/* This drops the fpriv->fbs reference. */
2017-02-28 17:46:40 +03:00
drm_framebuffer_put ( fb ) ;
2016-08-15 17:07:02 +03:00
}
}
if ( ! list_empty ( & arg . fbs ) ) {
INIT_WORK_ONSTACK ( & arg . work , drm_mode_rmfb_work_fn ) ;
schedule_work ( & arg . work ) ;
flush_work ( & arg . work ) ;
destroy_work_on_stack ( & arg . work ) ;
}
}
void drm_framebuffer_free ( struct kref * kref )
{
struct drm_framebuffer * fb =
container_of ( kref , struct drm_framebuffer , base . refcount ) ;
struct drm_device * dev = fb - > dev ;
/*
* The lookup idr holds a weak reference , which has not necessarily been
* removed at this point . Check for that .
*/
drm_mode_object_unregister ( dev , & fb - > base ) ;
fb - > funcs - > destroy ( fb ) ;
}
/**
* drm_framebuffer_init - initialize a framebuffer
* @ dev : DRM device
* @ fb : framebuffer to be initialized
* @ funcs : . . . with these functions
*
* Allocates an ID for the framebuffer ' s parent mode object , sets its mode
* functions & device file and adds it to the master fd list .
*
* IMPORTANT :
* This functions publishes the fb and makes it available for concurrent access
* by other users . Which means by this point the fb _must_ be fully set up -
* since all the fb attributes are invariant over its lifetime , no further
* locking but only correct reference counting is required .
*
* Returns :
* Zero on success , error code on failure .
*/
int drm_framebuffer_init ( struct drm_device * dev , struct drm_framebuffer * fb ,
const struct drm_framebuffer_funcs * funcs )
{
int ret ;
2016-11-18 22:52:58 +03:00
if ( WARN_ON_ONCE ( fb - > dev ! = dev | | ! fb - > format ) )
2016-11-18 22:52:54 +03:00
return - EINVAL ;
2016-08-15 17:07:02 +03:00
INIT_LIST_HEAD ( & fb - > filp_head ) ;
2016-11-18 22:52:54 +03:00
2016-08-15 17:07:02 +03:00
fb - > funcs = funcs ;
2017-12-20 12:35:44 +03:00
strcpy ( fb - > comm , current - > comm ) ;
2016-08-15 17:07:02 +03:00
2017-02-28 17:46:37 +03:00
ret = __drm_mode_object_add ( dev , & fb - > base , DRM_MODE_OBJECT_FB ,
false , drm_framebuffer_free ) ;
2016-08-15 17:07:02 +03:00
if ( ret )
goto out ;
mutex_lock ( & dev - > mode_config . fb_lock ) ;
dev - > mode_config . num_fb + + ;
list_add ( & fb - > head , & dev - > mode_config . fb_list ) ;
mutex_unlock ( & dev - > mode_config . fb_lock ) ;
drm_mode_object_register ( dev , & fb - > base ) ;
out :
return ret ;
}
EXPORT_SYMBOL ( drm_framebuffer_init ) ;
/**
* drm_framebuffer_lookup - look up a drm framebuffer and grab a reference
* @ dev : drm device
2017-11-09 02:35:04 +03:00
* @ file_priv : drm file to check for lease against .
2016-08-15 17:07:02 +03:00
* @ id : id of the fb object
*
* If successful , this grabs an additional reference to the framebuffer -
* callers need to make sure to eventually unreference the returned framebuffer
2017-02-28 17:46:40 +03:00
* again , using drm_framebuffer_put ( ) .
2016-08-15 17:07:02 +03:00
*/
struct drm_framebuffer * drm_framebuffer_lookup ( struct drm_device * dev ,
2017-03-15 09:25:07 +03:00
struct drm_file * file_priv ,
2016-08-15 17:07:02 +03:00
uint32_t id )
{
struct drm_mode_object * obj ;
struct drm_framebuffer * fb = NULL ;
2017-03-15 09:25:07 +03:00
obj = __drm_mode_object_find ( dev , file_priv , id , DRM_MODE_OBJECT_FB ) ;
2016-08-15 17:07:02 +03:00
if ( obj )
fb = obj_to_fb ( obj ) ;
return fb ;
}
EXPORT_SYMBOL ( drm_framebuffer_lookup ) ;
/**
* drm_framebuffer_unregister_private - unregister a private fb from the lookup idr
* @ fb : fb to unregister
*
* Drivers need to call this when cleaning up driver - private framebuffers , e . g .
* those used for fbdev . Note that the caller must hold a reference of it ' s own ,
* i . e . the object may not be destroyed through this call ( since it ' ll lead to a
* locking inversion ) .
2016-10-31 14:59:56 +03:00
*
* NOTE : This function is deprecated . For driver - private framebuffers it is not
* recommended to embed a framebuffer struct info fbdev struct , instead , a
2017-02-28 17:46:40 +03:00
* framebuffer pointer is preferred and drm_framebuffer_put ( ) should be called
* when the framebuffer is to be cleaned up .
2016-08-15 17:07:02 +03:00
*/
void drm_framebuffer_unregister_private ( struct drm_framebuffer * fb )
{
struct drm_device * dev ;
if ( ! fb )
return ;
dev = fb - > dev ;
/* Mark fb as reaped and drop idr ref. */
drm_mode_object_unregister ( dev , & fb - > base ) ;
}
EXPORT_SYMBOL ( drm_framebuffer_unregister_private ) ;
/**
* drm_framebuffer_cleanup - remove a framebuffer object
* @ fb : framebuffer to remove
*
* Cleanup framebuffer . This function is intended to be used from the drivers
2017-01-25 09:26:45 +03:00
* & drm_framebuffer_funcs . destroy callback . It can also be used to clean up
* driver private framebuffers embedded into a larger structure .
2016-08-15 17:07:02 +03:00
*
2017-01-25 09:26:45 +03:00
* Note that this function does not remove the fb from active usage - if it is
2016-08-15 17:07:02 +03:00
* still used anywhere , hilarity can ensue since userspace could call getfb on
* the id and get back - EINVAL . Obviously no concern at driver unload time .
*
* Also , the framebuffer will not be removed from the lookup idr - for
* user - created framebuffers this will happen in in the rmfb ioctl . For
* driver - private objects ( e . g . for fbdev ) drivers need to explicitly call
* drm_framebuffer_unregister_private .
*/
void drm_framebuffer_cleanup ( struct drm_framebuffer * fb )
{
struct drm_device * dev = fb - > dev ;
mutex_lock ( & dev - > mode_config . fb_lock ) ;
list_del ( & fb - > head ) ;
dev - > mode_config . num_fb - - ;
mutex_unlock ( & dev - > mode_config . fb_lock ) ;
}
EXPORT_SYMBOL ( drm_framebuffer_cleanup ) ;
2017-04-03 11:33:03 +03:00
static int atomic_remove_fb ( struct drm_framebuffer * fb )
{
struct drm_modeset_acquire_ctx ctx ;
struct drm_device * dev = fb - > dev ;
struct drm_atomic_state * state ;
struct drm_plane * plane ;
struct drm_connector * conn ;
struct drm_connector_state * conn_state ;
2017-11-01 18:04:33 +03:00
int i , ret ;
2017-04-03 11:33:03 +03:00
unsigned plane_mask ;
2017-11-01 18:04:33 +03:00
bool disable_crtcs = false ;
2017-04-03 11:33:03 +03:00
2017-11-01 18:04:33 +03:00
retry_disable :
2017-04-03 11:33:03 +03:00
drm_modeset_acquire_init ( & ctx , 0 ) ;
2017-11-01 18:04:33 +03:00
state = drm_atomic_state_alloc ( dev ) ;
if ( ! state ) {
ret = - ENOMEM ;
goto out ;
}
2017-04-03 11:33:03 +03:00
state - > acquire_ctx = & ctx ;
retry :
plane_mask = 0 ;
ret = drm_modeset_lock_all_ctx ( dev , & ctx ) ;
if ( ret )
goto unlock ;
drm_for_each_plane ( plane , dev ) {
struct drm_plane_state * plane_state ;
if ( plane - > state - > fb ! = fb )
continue ;
plane_state = drm_atomic_get_plane_state ( state , plane ) ;
if ( IS_ERR ( plane_state ) ) {
ret = PTR_ERR ( plane_state ) ;
goto unlock ;
}
2017-11-01 18:04:33 +03:00
if ( disable_crtcs & & plane_state - > crtc - > primary = = plane ) {
2017-04-03 11:33:03 +03:00
struct drm_crtc_state * crtc_state ;
crtc_state = drm_atomic_get_existing_crtc_state ( state , plane_state - > crtc ) ;
ret = drm_atomic_add_affected_connectors ( state , plane_state - > crtc ) ;
if ( ret )
goto unlock ;
crtc_state - > active = false ;
ret = drm_atomic_set_mode_for_crtc ( crtc_state , NULL ) ;
if ( ret )
goto unlock ;
}
drm_atomic_set_fb_for_plane ( plane_state , NULL ) ;
ret = drm_atomic_set_crtc_for_plane ( plane_state , NULL ) ;
if ( ret )
goto unlock ;
plane_mask | = BIT ( drm_plane_index ( plane ) ) ;
plane - > old_fb = plane - > fb ;
}
2017-11-01 18:04:33 +03:00
/* This list is only filled when disable_crtcs is set. */
2017-07-12 11:13:30 +03:00
for_each_new_connector_in_state ( state , conn , conn_state , i ) {
2017-04-03 11:33:03 +03:00
ret = drm_atomic_set_crtc_for_connector ( conn_state , NULL ) ;
if ( ret )
goto unlock ;
}
if ( plane_mask )
ret = drm_atomic_commit ( state ) ;
unlock :
if ( plane_mask )
drm_atomic_clean_old_fb ( dev , plane_mask , ret ) ;
if ( ret = = - EDEADLK ) {
2017-06-29 14:59:54 +03:00
drm_atomic_state_clear ( state ) ;
2017-04-03 11:33:03 +03:00
drm_modeset_backoff ( & ctx ) ;
goto retry ;
}
drm_atomic_state_put ( state ) ;
2017-11-01 18:04:33 +03:00
out :
2017-04-03 11:33:03 +03:00
drm_modeset_drop_locks ( & ctx ) ;
drm_modeset_acquire_fini ( & ctx ) ;
2017-11-01 18:04:33 +03:00
if ( ret = = - EINVAL & & ! disable_crtcs ) {
disable_crtcs = true ;
goto retry_disable ;
}
2017-04-03 11:33:03 +03:00
return ret ;
}
static void legacy_remove_fb ( struct drm_framebuffer * fb )
{
struct drm_device * dev = fb - > dev ;
struct drm_crtc * crtc ;
struct drm_plane * plane ;
drm_modeset_lock_all ( dev ) ;
/* remove from any CRTC */
drm_for_each_crtc ( crtc , dev ) {
if ( crtc - > primary - > fb = = fb ) {
/* should turn off the crtc */
if ( drm_crtc_force_disable ( crtc ) )
DRM_ERROR ( " failed to reset crtc %p when fb was deleted \n " , crtc ) ;
}
}
drm_for_each_plane ( plane , dev ) {
if ( plane - > fb = = fb )
drm_plane_force_disable ( plane ) ;
}
drm_modeset_unlock_all ( dev ) ;
}
2016-08-15 17:07:02 +03:00
/**
* drm_framebuffer_remove - remove and unreference a framebuffer object
* @ fb : framebuffer to remove
*
* Scans all the CRTCs and planes in @ dev ' s mode_config . If they ' re
* using @ fb , removes it , setting it to NULL . Then drops the reference to the
* passed - in framebuffer . Might take the modeset locks .
*
* Note that this function optimizes the cleanup away if the caller holds the
* last reference to the framebuffer . It is also guaranteed to not take the
* modeset locks in this case .
*/
void drm_framebuffer_remove ( struct drm_framebuffer * fb )
{
struct drm_device * dev ;
if ( ! fb )
return ;
dev = fb - > dev ;
WARN_ON ( ! list_empty ( & fb - > filp_head ) ) ;
/*
* drm ABI mandates that we remove any deleted framebuffers from active
* useage . But since most sane clients only remove framebuffers they no
* longer need , try to optimize this away .
*
* Since we ' re holding a reference ourselves , observing a refcount of 1
* means that we ' re the last holder and can skip it . Also , the refcount
* can never increase from 1 again , so we don ' t need any barriers or
* locks .
*
* Note that userspace could try to race with use and instate a new
* usage _after_ we ' ve cleared all current ones . End result will be an
* in - use fb with fb - id = = 0. Userspace is allowed to shoot its own foot
* in this manner .
*/
if ( drm_framebuffer_read_refcount ( fb ) > 1 ) {
2017-02-21 16:51:41 +03:00
if ( drm_drv_uses_atomic_modeset ( dev ) ) {
2017-04-03 11:33:03 +03:00
int ret = atomic_remove_fb ( fb ) ;
2017-02-21 16:51:41 +03:00
WARN ( ret , " atomic remove_fb failed with %i \n " , ret ) ;
2017-04-03 11:33:03 +03:00
} else
legacy_remove_fb ( fb ) ;
2016-08-15 17:07:02 +03:00
}
2017-02-28 17:46:40 +03:00
drm_framebuffer_put ( fb ) ;
2016-08-15 17:07:02 +03:00
}
EXPORT_SYMBOL ( drm_framebuffer_remove ) ;
2016-11-18 22:53:05 +03:00
/**
* drm_framebuffer_plane_width - width of the plane given the first plane
* @ width : width of the first plane
* @ fb : the framebuffer
* @ plane : plane index
*
* Returns :
* The width of @ plane , given that the width of the first plane is @ width .
*/
int drm_framebuffer_plane_width ( int width ,
const struct drm_framebuffer * fb , int plane )
{
if ( plane > = fb - > format - > num_planes )
return 0 ;
2017-03-21 21:12:14 +03:00
return fb_plane_width ( width , fb - > format , plane ) ;
2016-11-18 22:53:05 +03:00
}
EXPORT_SYMBOL ( drm_framebuffer_plane_width ) ;
/**
* drm_framebuffer_plane_height - height of the plane given the first plane
* @ height : height of the first plane
* @ fb : the framebuffer
* @ plane : plane index
*
* Returns :
* The height of @ plane , given that the height of the first plane is @ height .
*/
int drm_framebuffer_plane_height ( int height ,
const struct drm_framebuffer * fb , int plane )
{
if ( plane > = fb - > format - > num_planes )
return 0 ;
2017-03-21 21:12:14 +03:00
return fb_plane_height ( height , fb - > format , plane ) ;
2016-11-18 22:53:05 +03:00
}
EXPORT_SYMBOL ( drm_framebuffer_plane_height ) ;
2017-11-07 22:13:40 +03:00
void drm_framebuffer_print_info ( struct drm_printer * p , unsigned int indent ,
const struct drm_framebuffer * fb )
{
struct drm_format_name_buf format_name ;
unsigned int i ;
2017-12-20 12:35:44 +03:00
drm_printf_indent ( p , indent , " allocated by = %s \n " , fb - > comm ) ;
2017-11-07 22:13:40 +03:00
drm_printf_indent ( p , indent , " refcount=%u \n " ,
drm_framebuffer_read_refcount ( fb ) ) ;
drm_printf_indent ( p , indent , " format=%s \n " ,
drm_get_format_name ( fb - > format - > format , & format_name ) ) ;
drm_printf_indent ( p , indent , " modifier=0x%llx \n " , fb - > modifier ) ;
drm_printf_indent ( p , indent , " size=%ux%u \n " , fb - > width , fb - > height ) ;
drm_printf_indent ( p , indent , " layers: \n " ) ;
for ( i = 0 ; i < fb - > format - > num_planes ; i + + ) {
drm_printf_indent ( p , indent + 1 , " size[%u]=%dx%d \n " , i ,
drm_framebuffer_plane_width ( fb - > width , fb , i ) ,
drm_framebuffer_plane_height ( fb - > height , fb , i ) ) ;
drm_printf_indent ( p , indent + 1 , " pitch[%u]=%u \n " , i , fb - > pitches [ i ] ) ;
drm_printf_indent ( p , indent + 1 , " offset[%u]=%u \n " , i , fb - > offsets [ i ] ) ;
drm_printf_indent ( p , indent + 1 , " obj[%u]:%s \n " , i ,
fb - > obj [ i ] ? " " : " (null) " ) ;
if ( fb - > obj [ i ] )
drm_gem_print_info ( p , indent + 2 , fb - > obj [ i ] ) ;
}
}
# ifdef CONFIG_DEBUG_FS
static int drm_framebuffer_info ( struct seq_file * m , void * data )
{
struct drm_info_node * node = m - > private ;
struct drm_device * dev = node - > minor - > dev ;
struct drm_printer p = drm_seq_file_printer ( m ) ;
struct drm_framebuffer * fb ;
mutex_lock ( & dev - > mode_config . fb_lock ) ;
drm_for_each_fb ( fb , dev ) {
drm_printf ( & p , " framebuffer[%u]: \n " , fb - > base . id ) ;
drm_framebuffer_print_info ( & p , 1 , fb ) ;
}
mutex_unlock ( & dev - > mode_config . fb_lock ) ;
return 0 ;
}
static const struct drm_info_list drm_framebuffer_debugfs_list [ ] = {
{ " framebuffer " , drm_framebuffer_info , 0 } ,
} ;
int drm_framebuffer_debugfs_init ( struct drm_minor * minor )
{
return drm_debugfs_create_files ( drm_framebuffer_debugfs_list ,
ARRAY_SIZE ( drm_framebuffer_debugfs_list ) ,
minor - > debugfs_root , minor ) ;
}
# endif