2018-07-03 18:03:47 +02:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2018 Noralf Trønnes
*/
# include <linux/list.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/seq_file.h>
# include <linux/slab.h>
# include <drm/drm_client.h>
# include <drm/drm_debugfs.h>
# include <drm/drm_device.h>
# include <drm/drm_drv.h>
# include <drm/drm_file.h>
# include <drm/drm_fourcc.h>
2019-05-26 19:35:35 +02:00
# include <drm/drm_framebuffer.h>
2018-07-03 18:03:47 +02:00
# include <drm/drm_gem.h>
# include <drm/drm_mode.h>
# include <drm/drm_print.h>
# include "drm_crtc_internal.h"
# include "drm_internal.h"
/**
* DOC : overview
*
* This library provides support for clients running in the kernel like fbdev and bootsplash .
*
* GEM drivers which provide a GEM based dumb buffer with a virtual address are supported .
*/
static int drm_client_open ( struct drm_client_dev * client )
{
struct drm_device * dev = client - > dev ;
struct drm_file * file ;
file = drm_file_alloc ( dev - > primary ) ;
if ( IS_ERR ( file ) )
return PTR_ERR ( file ) ;
mutex_lock ( & dev - > filelist_mutex ) ;
list_add ( & file - > lhead , & dev - > filelist_internal ) ;
mutex_unlock ( & dev - > filelist_mutex ) ;
client - > file = file ;
return 0 ;
}
static void drm_client_close ( struct drm_client_dev * client )
{
struct drm_device * dev = client - > dev ;
mutex_lock ( & dev - > filelist_mutex ) ;
list_del ( & client - > file - > lhead ) ;
mutex_unlock ( & dev - > filelist_mutex ) ;
drm_file_free ( client - > file ) ;
}
/**
2018-10-01 21:45:36 +02:00
* drm_client_init - Initialise a DRM client
2018-07-03 18:03:47 +02:00
* @ dev : DRM device
* @ client : DRM client
* @ name : Client name
* @ funcs : DRM client functions ( optional )
*
2019-04-03 14:56:58 +02:00
* This initialises the client and opens a & drm_file .
* Use drm_client_register ( ) to complete the process .
2018-07-03 18:03:47 +02:00
* The caller needs to hold a reference on @ dev before calling this function .
* The client is freed when the & drm_device is unregistered . See drm_client_release ( ) .
*
* Returns :
* Zero on success or negative error code on failure .
*/
2018-10-01 21:45:36 +02:00
int drm_client_init ( struct drm_device * dev , struct drm_client_dev * client ,
const char * name , const struct drm_client_funcs * funcs )
2018-07-03 18:03:47 +02:00
{
int ret ;
2018-11-10 15:56:45 +01:00
if ( ! drm_core_check_feature ( dev , DRIVER_MODESET ) | | ! dev - > driver - > dumb_create )
2018-09-13 20:20:50 +01:00
return - EOPNOTSUPP ;
2018-07-03 18:03:47 +02:00
if ( funcs & & ! try_module_get ( funcs - > owner ) )
return - ENODEV ;
client - > dev = dev ;
client - > name = name ;
client - > funcs = funcs ;
2019-05-31 16:01:11 +02:00
ret = drm_client_modeset_create ( client ) ;
2018-07-03 18:03:47 +02:00
if ( ret )
goto err_put_module ;
2019-05-31 16:01:11 +02:00
ret = drm_client_open ( client ) ;
if ( ret )
goto err_free ;
2018-07-03 18:03:47 +02:00
drm_dev_get ( dev ) ;
return 0 ;
2019-05-31 16:01:11 +02:00
err_free :
drm_client_modeset_free ( client ) ;
2018-07-03 18:03:47 +02:00
err_put_module :
if ( funcs )
module_put ( funcs - > owner ) ;
return ret ;
}
2018-10-01 21:45:36 +02:00
EXPORT_SYMBOL ( drm_client_init ) ;
/**
2019-04-03 14:56:58 +02:00
* drm_client_register - Register client
2018-10-01 21:45:36 +02:00
* @ client : DRM client
*
* Add the client to the & drm_device client list to activate its callbacks .
* @ client must be initialized by a call to drm_client_init ( ) . After
2019-04-03 14:56:58 +02:00
* drm_client_register ( ) it is no longer permissible to call drm_client_release ( )
2018-10-01 21:45:36 +02:00
* directly ( outside the unregister callback ) , instead cleanup will happen
* automatically on driver unload .
*/
2019-04-03 14:56:58 +02:00
void drm_client_register ( struct drm_client_dev * client )
2018-10-01 21:45:36 +02:00
{
struct drm_device * dev = client - > dev ;
mutex_lock ( & dev - > clientlist_mutex ) ;
list_add ( & client - > list , & dev - > clientlist ) ;
mutex_unlock ( & dev - > clientlist_mutex ) ;
}
2019-04-03 14:56:58 +02:00
EXPORT_SYMBOL ( drm_client_register ) ;
2018-07-03 18:03:47 +02:00
/**
* drm_client_release - Release DRM client resources
* @ client : DRM client
*
2018-10-01 21:45:36 +02:00
* Releases resources by closing the & drm_file that was opened by drm_client_init ( ) .
2018-07-03 18:03:47 +02:00
* It is called automatically if the & drm_client_funcs . unregister callback is _not_ set .
*
* This function should only be called from the unregister callback . An exception
* is fbdev which cannot free the buffer if userspace has open file descriptors .
*
* Note :
* Clients cannot initiate a release by themselves . This is done to keep the code simple .
* The driver has to be unloaded before the client can be unloaded .
*/
void drm_client_release ( struct drm_client_dev * client )
{
struct drm_device * dev = client - > dev ;
2019-12-10 14:30:44 +02:00
drm_dbg_kms ( dev , " %s \n " , client - > name ) ;
2018-07-03 18:03:47 +02:00
2019-05-31 16:01:11 +02:00
drm_client_modeset_free ( client ) ;
2018-07-03 18:03:47 +02:00
drm_client_close ( client ) ;
drm_dev_put ( dev ) ;
if ( client - > funcs )
module_put ( client - > funcs - > owner ) ;
}
EXPORT_SYMBOL ( drm_client_release ) ;
void drm_client_dev_unregister ( struct drm_device * dev )
{
struct drm_client_dev * client , * tmp ;
if ( ! drm_core_check_feature ( dev , DRIVER_MODESET ) )
return ;
mutex_lock ( & dev - > clientlist_mutex ) ;
list_for_each_entry_safe ( client , tmp , & dev - > clientlist , list ) {
list_del ( & client - > list ) ;
if ( client - > funcs & & client - > funcs - > unregister ) {
client - > funcs - > unregister ( client ) ;
} else {
drm_client_release ( client ) ;
kfree ( client ) ;
}
}
mutex_unlock ( & dev - > clientlist_mutex ) ;
}
/**
* drm_client_dev_hotplug - Send hotplug event to clients
* @ dev : DRM device
*
* This function calls the & drm_client_funcs . hotplug callback on the attached clients .
*
* drm_kms_helper_hotplug_event ( ) calls this function , so drivers that use it
* don ' t need to call this function themselves .
*/
void drm_client_dev_hotplug ( struct drm_device * dev )
{
struct drm_client_dev * client ;
int ret ;
if ( ! drm_core_check_feature ( dev , DRIVER_MODESET ) )
return ;
mutex_lock ( & dev - > clientlist_mutex ) ;
list_for_each_entry ( client , & dev - > clientlist , list ) {
if ( ! client - > funcs | | ! client - > funcs - > hotplug )
continue ;
ret = client - > funcs - > hotplug ( client ) ;
2019-12-10 14:30:44 +02:00
drm_dbg_kms ( dev , " %s: ret=%d \n " , client - > name , ret ) ;
2018-07-03 18:03:47 +02:00
}
mutex_unlock ( & dev - > clientlist_mutex ) ;
}
EXPORT_SYMBOL ( drm_client_dev_hotplug ) ;
void drm_client_dev_restore ( struct drm_device * dev )
{
struct drm_client_dev * client ;
int ret ;
if ( ! drm_core_check_feature ( dev , DRIVER_MODESET ) )
return ;
mutex_lock ( & dev - > clientlist_mutex ) ;
list_for_each_entry ( client , & dev - > clientlist , list ) {
if ( ! client - > funcs | | ! client - > funcs - > restore )
continue ;
ret = client - > funcs - > restore ( client ) ;
2019-12-10 14:30:44 +02:00
drm_dbg_kms ( dev , " %s: ret=%d \n " , client - > name , ret ) ;
2018-07-03 18:03:47 +02:00
if ( ! ret ) /* The first one to return zero gets the privilege to restore */
break ;
}
mutex_unlock ( & dev - > clientlist_mutex ) ;
}
static void drm_client_buffer_delete ( struct drm_client_buffer * buffer )
{
struct drm_device * dev = buffer - > client - > dev ;
2018-11-10 15:56:45 +01:00
drm_gem_vunmap ( buffer - > gem , buffer - > vaddr ) ;
2018-07-03 18:03:47 +02:00
if ( buffer - > gem )
drm_gem_object_put_unlocked ( buffer - > gem ) ;
2018-07-12 17:04:14 +02:00
if ( buffer - > handle )
drm_mode_destroy_dumb ( dev , buffer - > handle , buffer - > client - > file ) ;
2018-07-03 18:03:47 +02:00
kfree ( buffer ) ;
}
static struct drm_client_buffer *
drm_client_buffer_create ( struct drm_client_dev * client , u32 width , u32 height , u32 format )
{
2019-05-16 12:31:49 +02:00
const struct drm_format_info * info = drm_format_info ( format ) ;
2018-07-03 18:03:47 +02:00
struct drm_mode_create_dumb dumb_args = { } ;
struct drm_device * dev = client - > dev ;
struct drm_client_buffer * buffer ;
struct drm_gem_object * obj ;
int ret ;
buffer = kzalloc ( sizeof ( * buffer ) , GFP_KERNEL ) ;
if ( ! buffer )
return ERR_PTR ( - ENOMEM ) ;
buffer - > client = client ;
dumb_args . width = width ;
dumb_args . height = height ;
2019-05-16 12:31:52 +02:00
dumb_args . bpp = info - > cpp [ 0 ] * 8 ;
2018-07-03 18:03:47 +02:00
ret = drm_mode_create_dumb ( dev , & dumb_args , client - > file ) ;
if ( ret )
2018-07-12 17:04:14 +02:00
goto err_delete ;
2018-07-03 18:03:47 +02:00
buffer - > handle = dumb_args . handle ;
buffer - > pitch = dumb_args . pitch ;
obj = drm_gem_object_lookup ( client - > file , dumb_args . handle ) ;
if ( ! obj ) {
ret = - ENOENT ;
goto err_delete ;
}
buffer - > gem = obj ;
2019-07-03 09:58:18 +02:00
return buffer ;
err_delete :
drm_client_buffer_delete ( buffer ) ;
return ERR_PTR ( ret ) ;
}
/**
* drm_client_buffer_vmap - Map DRM client buffer into address space
* @ buffer : DRM client buffer
*
* This function maps a client buffer into kernel address space . If the
* buffer is already mapped , it returns the mapping ' s address .
*
* Client buffer mappings are not ref ' counted . Each call to
* drm_client_buffer_vmap ( ) should be followed by a call to
* drm_client_buffer_vunmap ( ) ; or the client buffer should be mapped
2019-07-03 09:58:24 +02:00
* throughout its lifetime .
2019-07-03 09:58:18 +02:00
*
* Returns :
* The mapped memory ' s address
*/
void * drm_client_buffer_vmap ( struct drm_client_buffer * buffer )
{
void * vaddr ;
if ( buffer - > vaddr )
return buffer - > vaddr ;
2018-07-03 18:03:47 +02:00
/*
* FIXME : The dependency on GEM here isn ' t required , we could
* convert the driver handle to a dma - buf instead and use the
* backend - agnostic dma - buf vmap support instead . This would
* require that the handle2fd prime ioctl is reworked to pull the
* fd_install step out of the driver backend hooks , to make that
* final step optional for internal users .
*/
2019-07-03 09:58:18 +02:00
vaddr = drm_gem_vmap ( buffer - > gem ) ;
if ( IS_ERR ( vaddr ) )
return vaddr ;
2018-07-03 18:03:47 +02:00
buffer - > vaddr = vaddr ;
2019-07-03 09:58:18 +02:00
return vaddr ;
}
EXPORT_SYMBOL ( drm_client_buffer_vmap ) ;
2018-07-03 18:03:47 +02:00
2019-07-03 09:58:18 +02:00
/**
* drm_client_buffer_vunmap - Unmap DRM client buffer
* @ buffer : DRM client buffer
*
2019-07-03 09:58:24 +02:00
* This function removes a client buffer ' s memory mapping . Calling this
* function is only required by clients that manage their buffer mappings
* by themselves .
2019-07-03 09:58:18 +02:00
*/
void drm_client_buffer_vunmap ( struct drm_client_buffer * buffer )
{
drm_gem_vunmap ( buffer - > gem , buffer - > vaddr ) ;
buffer - > vaddr = NULL ;
2018-07-03 18:03:47 +02:00
}
2019-07-03 09:58:18 +02:00
EXPORT_SYMBOL ( drm_client_buffer_vunmap ) ;
2018-07-03 18:03:47 +02:00
static void drm_client_buffer_rmfb ( struct drm_client_buffer * buffer )
{
int ret ;
if ( ! buffer - > fb )
return ;
ret = drm_mode_rmfb ( buffer - > client - > dev , buffer - > fb - > base . id , buffer - > client - > file ) ;
if ( ret )
2019-12-10 14:30:44 +02:00
drm_err ( buffer - > client - > dev ,
" Error removing FB:%u (%d) \n " , buffer - > fb - > base . id , ret ) ;
2018-07-03 18:03:47 +02:00
buffer - > fb = NULL ;
}
static int drm_client_buffer_addfb ( struct drm_client_buffer * buffer ,
u32 width , u32 height , u32 format )
{
struct drm_client_dev * client = buffer - > client ;
struct drm_mode_fb_cmd fb_req = { } ;
const struct drm_format_info * info ;
int ret ;
info = drm_format_info ( format ) ;
fb_req . bpp = info - > cpp [ 0 ] * 8 ;
fb_req . depth = info - > depth ;
fb_req . width = width ;
fb_req . height = height ;
fb_req . handle = buffer - > handle ;
fb_req . pitch = buffer - > pitch ;
ret = drm_mode_addfb ( client - > dev , & fb_req , client - > file ) ;
if ( ret )
return ret ;
buffer - > fb = drm_framebuffer_lookup ( client - > dev , buffer - > client - > file , fb_req . fb_id ) ;
if ( WARN_ON ( ! buffer - > fb ) )
return - ENOENT ;
/* drop the reference we picked up in framebuffer lookup */
drm_framebuffer_put ( buffer - > fb ) ;
strscpy ( buffer - > fb - > comm , client - > name , TASK_COMM_LEN ) ;
return 0 ;
}
/**
* drm_client_framebuffer_create - Create a client framebuffer
* @ client : DRM client
* @ width : Framebuffer width
* @ height : Framebuffer height
* @ format : Buffer format
*
* This function creates a & drm_client_buffer which consists of a
* & drm_framebuffer backed by a dumb buffer .
* Call drm_client_framebuffer_delete ( ) to free the buffer .
*
* Returns :
* Pointer to a client buffer or an error pointer on failure .
*/
struct drm_client_buffer *
drm_client_framebuffer_create ( struct drm_client_dev * client , u32 width , u32 height , u32 format )
{
struct drm_client_buffer * buffer ;
int ret ;
buffer = drm_client_buffer_create ( client , width , height , format ) ;
if ( IS_ERR ( buffer ) )
return buffer ;
ret = drm_client_buffer_addfb ( buffer , width , height , format ) ;
if ( ret ) {
drm_client_buffer_delete ( buffer ) ;
return ERR_PTR ( ret ) ;
}
return buffer ;
}
EXPORT_SYMBOL ( drm_client_framebuffer_create ) ;
/**
* drm_client_framebuffer_delete - Delete a client framebuffer
* @ buffer : DRM client buffer ( can be NULL )
*/
void drm_client_framebuffer_delete ( struct drm_client_buffer * buffer )
{
if ( ! buffer )
return ;
drm_client_buffer_rmfb ( buffer ) ;
drm_client_buffer_delete ( buffer ) ;
}
EXPORT_SYMBOL ( drm_client_framebuffer_delete ) ;
2018-07-03 18:03:51 +02:00
# ifdef CONFIG_DEBUG_FS
static int drm_client_debugfs_internal_clients ( 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_client_dev * client ;
mutex_lock ( & dev - > clientlist_mutex ) ;
list_for_each_entry ( client , & dev - > clientlist , list )
drm_printf ( & p , " %s \n " , client - > name ) ;
mutex_unlock ( & dev - > clientlist_mutex ) ;
return 0 ;
}
static const struct drm_info_list drm_client_debugfs_list [ ] = {
{ " internal_clients " , drm_client_debugfs_internal_clients , 0 } ,
} ;
int drm_client_debugfs_init ( struct drm_minor * minor )
{
return drm_debugfs_create_files ( drm_client_debugfs_list ,
ARRAY_SIZE ( drm_client_debugfs_list ) ,
minor - > debugfs_root , minor ) ;
}
# endif