2021-10-05 23:23:14 +03:00
// SPDX-License-Identifier: MIT
/*
* Copyright ( C ) 2020 - 2021 Red Hat , Inc .
*
* Authors :
* Hans de Goede < hdegoede @ redhat . com >
*/
# include <linux/device.h>
# include <linux/kernel.h>
# include <linux/list.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/slab.h>
# include <drm/drm_privacy_screen_machine.h>
# include <drm/drm_privacy_screen_consumer.h>
# include <drm/drm_privacy_screen_driver.h>
# include "drm_internal.h"
/**
* DOC : overview
*
* This class allows non KMS drivers , from e . g . drivers / platform / x86 to
* register a privacy - screen device , which the KMS drivers can then use
* to implement the standard privacy - screen properties , see
* : ref : ` Standard Connector Properties < standard_connector_properties > ` .
*
* KMS drivers using a privacy - screen class device are advised to use the
* drm_connector_attach_privacy_screen_provider ( ) and
* drm_connector_update_privacy_screen ( ) helpers for dealing with this .
*/
# define to_drm_privacy_screen(dev) \
container_of ( dev , struct drm_privacy_screen , dev )
static DEFINE_MUTEX ( drm_privacy_screen_lookup_lock ) ;
static LIST_HEAD ( drm_privacy_screen_lookup_list ) ;
static DEFINE_MUTEX ( drm_privacy_screen_devs_lock ) ;
static LIST_HEAD ( drm_privacy_screen_devs ) ;
/*** drm_privacy_screen_machine.h functions ***/
/**
* drm_privacy_screen_lookup_add - add an entry to the static privacy - screen
* lookup list
* @ lookup : lookup list entry to add
*
* Add an entry to the static privacy - screen lookup list . Note the
* & struct list_head which is part of the & struct drm_privacy_screen_lookup
* gets added to a list owned by the privacy - screen core . So the passed in
* & struct drm_privacy_screen_lookup must not be free - ed until it is removed
* from the lookup list by calling drm_privacy_screen_lookup_remove ( ) .
*/
void drm_privacy_screen_lookup_add ( struct drm_privacy_screen_lookup * lookup )
{
mutex_lock ( & drm_privacy_screen_lookup_lock ) ;
list_add ( & lookup - > list , & drm_privacy_screen_lookup_list ) ;
mutex_unlock ( & drm_privacy_screen_lookup_lock ) ;
}
EXPORT_SYMBOL ( drm_privacy_screen_lookup_add ) ;
/**
* drm_privacy_screen_lookup_remove - remove an entry to the static
* privacy - screen lookup list
* @ lookup : lookup list entry to remove
*
* Remove an entry previously added with drm_privacy_screen_lookup_add ( )
* from the static privacy - screen lookup list .
*/
void drm_privacy_screen_lookup_remove ( struct drm_privacy_screen_lookup * lookup )
{
mutex_lock ( & drm_privacy_screen_lookup_lock ) ;
list_del ( & lookup - > list ) ;
mutex_unlock ( & drm_privacy_screen_lookup_lock ) ;
}
EXPORT_SYMBOL ( drm_privacy_screen_lookup_remove ) ;
/*** drm_privacy_screen_consumer.h functions ***/
static struct drm_privacy_screen * drm_privacy_screen_get_by_name (
const char * name )
{
struct drm_privacy_screen * priv ;
struct device * dev = NULL ;
mutex_lock ( & drm_privacy_screen_devs_lock ) ;
list_for_each_entry ( priv , & drm_privacy_screen_devs , list ) {
if ( strcmp ( dev_name ( & priv - > dev ) , name ) = = 0 ) {
dev = get_device ( & priv - > dev ) ;
break ;
}
}
mutex_unlock ( & drm_privacy_screen_devs_lock ) ;
return dev ? to_drm_privacy_screen ( dev ) : NULL ;
}
/**
* drm_privacy_screen_get - get a privacy - screen provider
* @ dev : consumer - device for which to get a privacy - screen provider
* @ con_id : ( video ) connector name for which to get a privacy - screen provider
*
* Get a privacy - screen provider for a privacy - screen attached to the
* display described by the @ dev and @ con_id parameters .
*
* Return :
* * A pointer to a & struct drm_privacy_screen on success .
* * ERR_PTR ( - ENODEV ) if no matching privacy - screen is found
* * ERR_PTR ( - EPROBE_DEFER ) if there is a matching privacy - screen ,
* but it has not been registered yet .
*/
struct drm_privacy_screen * drm_privacy_screen_get ( struct device * dev ,
const char * con_id )
{
const char * dev_id = dev ? dev_name ( dev ) : NULL ;
struct drm_privacy_screen_lookup * l ;
struct drm_privacy_screen * priv ;
const char * provider = NULL ;
int match , best = - 1 ;
/*
* For now we only support using a static lookup table , which is
* populated by the drm_privacy_screen_arch_init ( ) call . This should
* be extended with device - tree / fw_node lookup when support is added
* for device - tree using hardware with a privacy - screen .
*
* The lookup algorithm was shamelessly taken from the clock
* framework :
*
* We do slightly fuzzy matching here :
* An entry with a NULL ID is assumed to be a wildcard .
* If an entry has a device ID , it must match
* If an entry has a connection ID , it must match
* Then we take the most specific entry - with the following order
* of precedence : dev + con > dev only > con only .
*/
mutex_lock ( & drm_privacy_screen_lookup_lock ) ;
list_for_each_entry ( l , & drm_privacy_screen_lookup_list , list ) {
match = 0 ;
if ( l - > dev_id ) {
if ( ! dev_id | | strcmp ( l - > dev_id , dev_id ) )
continue ;
match + = 2 ;
}
if ( l - > con_id ) {
if ( ! con_id | | strcmp ( l - > con_id , con_id ) )
continue ;
match + = 1 ;
}
if ( match > best ) {
provider = l - > provider ;
best = match ;
}
}
mutex_unlock ( & drm_privacy_screen_lookup_lock ) ;
if ( ! provider )
return ERR_PTR ( - ENODEV ) ;
priv = drm_privacy_screen_get_by_name ( provider ) ;
if ( ! priv )
return ERR_PTR ( - EPROBE_DEFER ) ;
return priv ;
}
EXPORT_SYMBOL ( drm_privacy_screen_get ) ;
/**
* drm_privacy_screen_put - release a privacy - screen reference
* @ priv : privacy screen reference to release
*
* Release a privacy - screen provider reference gotten through
* drm_privacy_screen_get ( ) . May be called with a NULL or ERR_PTR ,
* in which case it is a no - op .
*/
void drm_privacy_screen_put ( struct drm_privacy_screen * priv )
{
if ( IS_ERR_OR_NULL ( priv ) )
return ;
put_device ( & priv - > dev ) ;
}
EXPORT_SYMBOL ( drm_privacy_screen_put ) ;
/**
* drm_privacy_screen_set_sw_state - set a privacy - screen ' s sw - state
* @ priv : privacy screen to set the sw - state for
* @ sw_state : new sw - state value to set
*
* Set the sw - state of a privacy screen . If the privacy - screen is not
* in a locked hw - state , then the actual and hw - state of the privacy - screen
* will be immediately updated to the new value . If the privacy - screen is
* in a locked hw - state , then the new sw - state will be remembered as the
* requested state to put the privacy - screen in when it becomes unlocked .
*
* Return : 0 on success , negative error code on failure .
*/
int drm_privacy_screen_set_sw_state ( struct drm_privacy_screen * priv ,
enum drm_privacy_screen_status sw_state )
{
int ret = 0 ;
mutex_lock ( & priv - > lock ) ;
if ( ! priv - > ops ) {
ret = - ENODEV ;
goto out ;
}
/*
* As per the DRM connector properties documentation , setting the
* sw_state while the hw_state is locked is allowed . In this case
* it is a no - op other then storing the new sw_state so that it
* can be honored when the state gets unlocked .
* Also skip the set if the hw already is in the desired state .
*/
if ( priv - > hw_state > = PRIVACY_SCREEN_DISABLED_LOCKED | |
priv - > hw_state = = sw_state ) {
priv - > sw_state = sw_state ;
goto out ;
}
ret = priv - > ops - > set_sw_state ( priv , sw_state ) ;
out :
mutex_unlock ( & priv - > lock ) ;
return ret ;
}
EXPORT_SYMBOL ( drm_privacy_screen_set_sw_state ) ;
/**
* drm_privacy_screen_get_state - get privacy - screen ' s current state
* @ priv : privacy screen to get the state for
* @ sw_state_ret : address where to store the privacy - screens current sw - state
* @ hw_state_ret : address where to store the privacy - screens current hw - state
*
* Get the current state of a privacy - screen , both the sw - state and the
* hw - state .
*/
void drm_privacy_screen_get_state ( struct drm_privacy_screen * priv ,
enum drm_privacy_screen_status * sw_state_ret ,
enum drm_privacy_screen_status * hw_state_ret )
{
mutex_lock ( & priv - > lock ) ;
* sw_state_ret = priv - > sw_state ;
* hw_state_ret = priv - > hw_state ;
mutex_unlock ( & priv - > lock ) ;
}
EXPORT_SYMBOL ( drm_privacy_screen_get_state ) ;
2021-10-05 23:23:16 +03:00
/**
* drm_privacy_screen_register_notifier - register a notifier
* @ priv : Privacy screen to register the notifier with
* @ nb : Notifier - block for the notifier to register
*
* Register a notifier with the privacy - screen to be notified of changes made
* to the privacy - screen state from outside of the privacy - screen class .
* E . g . the state may be changed by the hardware itself in response to a
* hotkey press .
*
* The notifier is called with no locks held . The new hw_state and sw_state
* can be retrieved using the drm_privacy_screen_get_state ( ) function .
2022-02-07 16:04:07 +03:00
* A pointer to the drm_privacy_screen ' s struct is passed as the ` ` void * data ` `
2021-10-05 23:23:16 +03:00
* argument of the notifier_block ' s notifier_call .
*
* The notifier will NOT be called when changes are made through
* drm_privacy_screen_set_sw_state ( ) . It is only called for external changes .
*
* Return : 0 on success , negative error code on failure .
*/
int drm_privacy_screen_register_notifier ( struct drm_privacy_screen * priv ,
struct notifier_block * nb )
{
return blocking_notifier_chain_register ( & priv - > notifier_head , nb ) ;
}
EXPORT_SYMBOL ( drm_privacy_screen_register_notifier ) ;
/**
* drm_privacy_screen_unregister_notifier - unregister a notifier
* @ priv : Privacy screen to register the notifier with
* @ nb : Notifier - block for the notifier to register
*
* Unregister a notifier registered with drm_privacy_screen_register_notifier ( ) .
*
* Return : 0 on success , negative error code on failure .
*/
int drm_privacy_screen_unregister_notifier ( struct drm_privacy_screen * priv ,
struct notifier_block * nb )
{
return blocking_notifier_chain_unregister ( & priv - > notifier_head , nb ) ;
}
EXPORT_SYMBOL ( drm_privacy_screen_unregister_notifier ) ;
2021-10-05 23:23:14 +03:00
/*** drm_privacy_screen_driver.h functions ***/
static ssize_t sw_state_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct drm_privacy_screen * priv = to_drm_privacy_screen ( dev ) ;
const char * const sw_state_names [ ] = {
" Disabled " ,
" Enabled " ,
} ;
ssize_t ret ;
mutex_lock ( & priv - > lock ) ;
if ( ! priv - > ops )
ret = - ENODEV ;
else if ( WARN_ON ( priv - > sw_state > = ARRAY_SIZE ( sw_state_names ) ) )
ret = - ENXIO ;
else
ret = sprintf ( buf , " %s \n " , sw_state_names [ priv - > sw_state ] ) ;
mutex_unlock ( & priv - > lock ) ;
return ret ;
}
/*
* RO : Do not allow setting the sw_state through sysfs , this MUST be done
* through the drm_properties on the drm_connector .
*/
static DEVICE_ATTR_RO ( sw_state ) ;
static ssize_t hw_state_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct drm_privacy_screen * priv = to_drm_privacy_screen ( dev ) ;
const char * const hw_state_names [ ] = {
" Disabled " ,
" Enabled " ,
" Disabled, locked " ,
" Enabled, locked " ,
} ;
ssize_t ret ;
mutex_lock ( & priv - > lock ) ;
if ( ! priv - > ops )
ret = - ENODEV ;
else if ( WARN_ON ( priv - > hw_state > = ARRAY_SIZE ( hw_state_names ) ) )
ret = - ENXIO ;
else
ret = sprintf ( buf , " %s \n " , hw_state_names [ priv - > hw_state ] ) ;
mutex_unlock ( & priv - > lock ) ;
return ret ;
}
static DEVICE_ATTR_RO ( hw_state ) ;
static struct attribute * drm_privacy_screen_attrs [ ] = {
& dev_attr_sw_state . attr ,
& dev_attr_hw_state . attr ,
NULL
} ;
ATTRIBUTE_GROUPS ( drm_privacy_screen ) ;
static struct device_type drm_privacy_screen_type = {
. name = " privacy_screen " ,
. groups = drm_privacy_screen_groups ,
} ;
static void drm_privacy_screen_device_release ( struct device * dev )
{
struct drm_privacy_screen * priv = to_drm_privacy_screen ( dev ) ;
kfree ( priv ) ;
}
/**
* drm_privacy_screen_register - register a privacy - screen
* @ parent : parent - device for the privacy - screen
* @ ops : & struct drm_privacy_screen_ops pointer with ops for the privacy - screen
2022-02-07 14:33:07 +03:00
* @ data : Private data owned by the privacy screen provider
2021-10-05 23:23:14 +03:00
*
* Create and register a privacy - screen .
*
* Return :
* * A pointer to the created privacy - screen on success .
* * An ERR_PTR ( errno ) on failure .
*/
struct drm_privacy_screen * drm_privacy_screen_register (
2022-01-07 22:02:06 +03:00
struct device * parent , const struct drm_privacy_screen_ops * ops ,
void * data )
2021-10-05 23:23:14 +03:00
{
struct drm_privacy_screen * priv ;
int ret ;
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return ERR_PTR ( - ENOMEM ) ;
mutex_init ( & priv - > lock ) ;
2021-10-05 23:23:16 +03:00
BLOCKING_INIT_NOTIFIER_HEAD ( & priv - > notifier_head ) ;
2021-10-05 23:23:14 +03:00
priv - > dev . class = drm_class ;
priv - > dev . type = & drm_privacy_screen_type ;
priv - > dev . parent = parent ;
priv - > dev . release = drm_privacy_screen_device_release ;
dev_set_name ( & priv - > dev , " privacy_screen-%s " , dev_name ( parent ) ) ;
2022-01-07 22:02:06 +03:00
priv - > drvdata = data ;
2021-10-05 23:23:14 +03:00
priv - > ops = ops ;
priv - > ops - > get_hw_state ( priv ) ;
ret = device_register ( & priv - > dev ) ;
if ( ret ) {
put_device ( & priv - > dev ) ;
return ERR_PTR ( ret ) ;
}
mutex_lock ( & drm_privacy_screen_devs_lock ) ;
list_add ( & priv - > list , & drm_privacy_screen_devs ) ;
mutex_unlock ( & drm_privacy_screen_devs_lock ) ;
return priv ;
}
EXPORT_SYMBOL ( drm_privacy_screen_register ) ;
/**
* drm_privacy_screen_unregister - unregister privacy - screen
* @ priv : privacy - screen to unregister
*
* Unregister a privacy - screen registered with drm_privacy_screen_register ( ) .
* May be called with a NULL or ERR_PTR , in which case it is a no - op .
*/
void drm_privacy_screen_unregister ( struct drm_privacy_screen * priv )
{
if ( IS_ERR_OR_NULL ( priv ) )
return ;
mutex_lock ( & drm_privacy_screen_devs_lock ) ;
list_del ( & priv - > list ) ;
mutex_unlock ( & drm_privacy_screen_devs_lock ) ;
mutex_lock ( & priv - > lock ) ;
2022-01-07 22:02:06 +03:00
priv - > drvdata = NULL ;
2021-10-05 23:23:14 +03:00
priv - > ops = NULL ;
mutex_unlock ( & priv - > lock ) ;
device_unregister ( & priv - > dev ) ;
}
EXPORT_SYMBOL ( drm_privacy_screen_unregister ) ;
2021-10-05 23:23:16 +03:00
/**
* drm_privacy_screen_call_notifier_chain - notify consumers of state change
* @ priv : Privacy screen to register the notifier with
*
* A privacy - screen provider driver can call this functions upon external
* changes to the privacy - screen state . E . g . the state may be changed by the
* hardware itself in response to a hotkey press .
* This function must be called without holding the privacy - screen lock .
* the driver must update sw_state and hw_state to reflect the new state before
* calling this function .
* The expected behavior from the driver upon receiving an external state
* change event is : 1. Take the lock ; 2. Update sw_state and hw_state ;
* 3. Release the lock . 4. Call drm_privacy_screen_call_notifier_chain ( ) .
*/
void drm_privacy_screen_call_notifier_chain ( struct drm_privacy_screen * priv )
{
blocking_notifier_call_chain ( & priv - > notifier_head , 0 , priv ) ;
}
EXPORT_SYMBOL ( drm_privacy_screen_call_notifier_chain ) ;