2022-02-10 17:05:21 +02:00
// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright(c) 2022 Intel Corporation. All rights reserved.
//
// Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
// Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
//
# include <linux/debugfs.h>
# include <linux/errno.h>
# include <linux/list.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/slab.h>
2022-05-06 16:26:44 +03:00
# include <sound/sof/ipc4/header.h>
2022-02-10 17:05:21 +02:00
# include "ops.h"
# include "sof-client.h"
# include "sof-priv.h"
/**
* struct sof_ipc_event_entry - IPC client event description
* @ ipc_msg_type : IPC msg type of the event the client is interested
* @ cdev : sof_client_dev of the requesting client
* @ callback : Callback function of the client
* @ list : item in SOF core client event list
*/
struct sof_ipc_event_entry {
u32 ipc_msg_type ;
struct sof_client_dev * cdev ;
sof_client_event_callback callback ;
struct list_head list ;
} ;
/**
* struct sof_state_event_entry - DSP panic event subscription entry
* @ cdev : sof_client_dev of the requesting client
* @ callback : Callback function of the client
* @ list : item in SOF core client event list
*/
struct sof_state_event_entry {
struct sof_client_dev * cdev ;
sof_client_fw_state_callback callback ;
struct list_head list ;
} ;
static void sof_client_auxdev_release ( struct device * dev )
{
struct auxiliary_device * auxdev = to_auxiliary_dev ( dev ) ;
struct sof_client_dev * cdev = auxiliary_dev_to_sof_client_dev ( auxdev ) ;
kfree ( cdev - > auxdev . dev . platform_data ) ;
kfree ( cdev ) ;
}
static int sof_client_dev_add_data ( struct sof_client_dev * cdev , const void * data ,
size_t size )
{
void * d = NULL ;
if ( data ) {
d = kmemdup ( data , size , GFP_KERNEL ) ;
if ( ! d )
return - ENOMEM ;
}
cdev - > auxdev . dev . platform_data = d ;
return 0 ;
}
2022-02-10 17:05:23 +02:00
# if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
static int sof_register_ipc_flood_test ( struct snd_sof_dev * sdev )
{
int ret = 0 ;
int i ;
2022-05-06 16:26:47 +03:00
if ( sdev - > pdata - > ipc_type ! = SOF_IPC )
return 0 ;
2022-02-10 17:05:23 +02:00
for ( i = 0 ; i < CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM ; i + + ) {
ret = sof_client_dev_register ( sdev , " ipc_flood " , i , NULL , 0 ) ;
if ( ret < 0 )
break ;
}
if ( ret ) {
for ( ; i > = 0 ; - - i )
sof_client_dev_unregister ( sdev , " ipc_flood " , i ) ;
}
return ret ;
}
static void sof_unregister_ipc_flood_test ( struct snd_sof_dev * sdev )
{
int i ;
for ( i = 0 ; i < CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM ; i + + )
sof_client_dev_unregister ( sdev , " ipc_flood " , i ) ;
}
# else
static inline int sof_register_ipc_flood_test ( struct snd_sof_dev * sdev )
{
return 0 ;
}
static inline void sof_unregister_ipc_flood_test ( struct snd_sof_dev * sdev ) { }
# endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST */
2022-02-10 17:05:24 +02:00
# if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR)
static int sof_register_ipc_msg_injector ( struct snd_sof_dev * sdev )
{
return sof_client_dev_register ( sdev , " msg_injector " , 0 , NULL , 0 ) ;
}
static void sof_unregister_ipc_msg_injector ( struct snd_sof_dev * sdev )
{
sof_client_dev_unregister ( sdev , " msg_injector " , 0 ) ;
}
# else
static inline int sof_register_ipc_msg_injector ( struct snd_sof_dev * sdev )
{
return 0 ;
}
static inline void sof_unregister_ipc_msg_injector ( struct snd_sof_dev * sdev ) { }
# endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR */
2022-02-10 17:05:21 +02:00
int sof_register_clients ( struct snd_sof_dev * sdev )
{
2022-02-10 17:05:23 +02:00
int ret ;
/* Register platform independent client devices */
ret = sof_register_ipc_flood_test ( sdev ) ;
if ( ret ) {
dev_err ( sdev - > dev , " IPC flood test client registration failed \n " ) ;
return ret ;
}
2022-02-10 17:05:24 +02:00
ret = sof_register_ipc_msg_injector ( sdev ) ;
if ( ret ) {
dev_err ( sdev - > dev , " IPC message injector client registration failed \n " ) ;
goto err_msg_injector ;
}
2022-02-10 17:05:23 +02:00
/* Platform depndent client device registration */
2022-02-10 17:05:21 +02:00
if ( sof_ops ( sdev ) & & sof_ops ( sdev ) - > register_ipc_clients )
2022-02-10 17:05:23 +02:00
ret = sof_ops ( sdev ) - > register_ipc_clients ( sdev ) ;
2022-02-10 17:05:21 +02:00
2022-02-10 17:05:24 +02:00
if ( ! ret )
return 0 ;
sof_unregister_ipc_msg_injector ( sdev ) ;
err_msg_injector :
sof_unregister_ipc_flood_test ( sdev ) ;
2022-02-10 17:05:23 +02:00
return ret ;
2022-02-10 17:05:21 +02:00
}
void sof_unregister_clients ( struct snd_sof_dev * sdev )
{
if ( sof_ops ( sdev ) & & sof_ops ( sdev ) - > unregister_ipc_clients )
sof_ops ( sdev ) - > unregister_ipc_clients ( sdev ) ;
2022-02-10 17:05:23 +02:00
2022-02-10 17:05:24 +02:00
sof_unregister_ipc_msg_injector ( sdev ) ;
2022-02-10 17:05:23 +02:00
sof_unregister_ipc_flood_test ( sdev ) ;
2022-02-10 17:05:21 +02:00
}
int sof_client_dev_register ( struct snd_sof_dev * sdev , const char * name , u32 id ,
const void * data , size_t size )
{
struct auxiliary_device * auxdev ;
struct sof_client_dev * cdev ;
int ret ;
cdev = kzalloc ( sizeof ( * cdev ) , GFP_KERNEL ) ;
if ( ! cdev )
return - ENOMEM ;
cdev - > sdev = sdev ;
auxdev = & cdev - > auxdev ;
auxdev - > name = name ;
auxdev - > dev . parent = sdev - > dev ;
auxdev - > dev . release = sof_client_auxdev_release ;
auxdev - > id = id ;
ret = sof_client_dev_add_data ( cdev , data , size ) ;
if ( ret < 0 )
goto err_dev_add_data ;
ret = auxiliary_device_init ( auxdev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev , " failed to initialize client dev %s.%d \n " , name , id ) ;
goto err_dev_init ;
}
ret = auxiliary_device_add ( & cdev - > auxdev ) ;
if ( ret < 0 ) {
dev_err ( sdev - > dev , " failed to add client dev %s.%d \n " , name , id ) ;
/*
* sof_client_auxdev_release ( ) will be invoked to free up memory
* allocations through put_device ( )
*/
auxiliary_device_uninit ( & cdev - > auxdev ) ;
return ret ;
}
/* add to list of SOF client devices */
mutex_lock ( & sdev - > ipc_client_mutex ) ;
list_add ( & cdev - > list , & sdev - > ipc_client_list ) ;
mutex_unlock ( & sdev - > ipc_client_mutex ) ;
return 0 ;
err_dev_init :
kfree ( cdev - > auxdev . dev . platform_data ) ;
err_dev_add_data :
kfree ( cdev ) ;
return ret ;
}
EXPORT_SYMBOL_NS_GPL ( sof_client_dev_register , SND_SOC_SOF_CLIENT ) ;
void sof_client_dev_unregister ( struct snd_sof_dev * sdev , const char * name , u32 id )
{
struct sof_client_dev * cdev ;
mutex_lock ( & sdev - > ipc_client_mutex ) ;
/*
* sof_client_auxdev_release ( ) will be invoked to free up memory
* allocations through put_device ( )
*/
list_for_each_entry ( cdev , & sdev - > ipc_client_list , list ) {
if ( ! strcmp ( cdev - > auxdev . name , name ) & & cdev - > auxdev . id = = id ) {
list_del ( & cdev - > list ) ;
auxiliary_device_delete ( & cdev - > auxdev ) ;
auxiliary_device_uninit ( & cdev - > auxdev ) ;
break ;
}
}
mutex_unlock ( & sdev - > ipc_client_mutex ) ;
}
EXPORT_SYMBOL_NS_GPL ( sof_client_dev_unregister , SND_SOC_SOF_CLIENT ) ;
int sof_client_ipc_tx_message ( struct sof_client_dev * cdev , void * ipc_msg ,
void * reply_data , size_t reply_bytes )
{
2022-05-06 16:26:44 +03:00
if ( cdev - > sdev - > pdata - > ipc_type = = SOF_IPC ) {
struct sof_ipc_cmd_hdr * hdr = ipc_msg ;
2022-02-10 17:05:21 +02:00
2022-05-06 16:26:44 +03:00
return sof_ipc_tx_message ( cdev - > sdev - > ipc , ipc_msg , hdr - > size ,
reply_data , reply_bytes ) ;
} else if ( cdev - > sdev - > pdata - > ipc_type = = SOF_INTEL_IPC4 ) {
struct sof_ipc4_msg * msg = ipc_msg ;
return sof_ipc_tx_message ( cdev - > sdev - > ipc , ipc_msg , msg - > data_size ,
reply_data , reply_bytes ) ;
}
return - EINVAL ;
2022-02-10 17:05:21 +02:00
}
EXPORT_SYMBOL_NS_GPL ( sof_client_ipc_tx_message , SND_SOC_SOF_CLIENT ) ;
2022-02-10 17:05:22 +02:00
int sof_suspend_clients ( struct snd_sof_dev * sdev , pm_message_t state )
{
struct auxiliary_driver * adrv ;
struct sof_client_dev * cdev ;
mutex_lock ( & sdev - > ipc_client_mutex ) ;
list_for_each_entry ( cdev , & sdev - > ipc_client_list , list ) {
/* Skip devices without loaded driver */
if ( ! cdev - > auxdev . dev . driver )
continue ;
adrv = to_auxiliary_drv ( cdev - > auxdev . dev . driver ) ;
if ( adrv - > suspend )
adrv - > suspend ( & cdev - > auxdev , state ) ;
}
mutex_unlock ( & sdev - > ipc_client_mutex ) ;
return 0 ;
}
EXPORT_SYMBOL_NS_GPL ( sof_suspend_clients , SND_SOC_SOF_CLIENT ) ;
int sof_resume_clients ( struct snd_sof_dev * sdev )
{
struct auxiliary_driver * adrv ;
struct sof_client_dev * cdev ;
mutex_lock ( & sdev - > ipc_client_mutex ) ;
list_for_each_entry ( cdev , & sdev - > ipc_client_list , list ) {
/* Skip devices without loaded driver */
if ( ! cdev - > auxdev . dev . driver )
continue ;
adrv = to_auxiliary_drv ( cdev - > auxdev . dev . driver ) ;
if ( adrv - > resume )
adrv - > resume ( & cdev - > auxdev ) ;
}
mutex_unlock ( & sdev - > ipc_client_mutex ) ;
return 0 ;
}
EXPORT_SYMBOL_NS_GPL ( sof_resume_clients , SND_SOC_SOF_CLIENT ) ;
2022-02-10 17:05:21 +02:00
struct dentry * sof_client_get_debugfs_root ( struct sof_client_dev * cdev )
{
return cdev - > sdev - > debugfs_root ;
}
EXPORT_SYMBOL_NS_GPL ( sof_client_get_debugfs_root , SND_SOC_SOF_CLIENT ) ;
/* DMA buffer allocation in client drivers must use the core SOF device */
struct device * sof_client_get_dma_dev ( struct sof_client_dev * cdev )
{
return cdev - > sdev - > dev ;
}
EXPORT_SYMBOL_NS_GPL ( sof_client_get_dma_dev , SND_SOC_SOF_CLIENT ) ;
const struct sof_ipc_fw_version * sof_client_get_fw_version ( struct sof_client_dev * cdev )
{
struct snd_sof_dev * sdev = sof_client_dev_to_sof_dev ( cdev ) ;
return & sdev - > fw_ready . version ;
}
EXPORT_SYMBOL_NS_GPL ( sof_client_get_fw_version , SND_SOC_SOF_CLIENT ) ;
2022-05-06 16:26:40 +03:00
size_t sof_client_get_ipc_max_payload_size ( struct sof_client_dev * cdev )
{
struct snd_sof_dev * sdev = sof_client_dev_to_sof_dev ( cdev ) ;
return sdev - > ipc - > max_payload_size ;
}
EXPORT_SYMBOL_NS_GPL ( sof_client_get_ipc_max_payload_size , SND_SOC_SOF_CLIENT ) ;
2022-05-06 16:26:43 +03:00
enum sof_ipc_type sof_client_get_ipc_type ( struct sof_client_dev * cdev )
{
struct snd_sof_dev * sdev = sof_client_dev_to_sof_dev ( cdev ) ;
return sdev - > pdata - > ipc_type ;
}
EXPORT_SYMBOL_NS_GPL ( sof_client_get_ipc_type , SND_SOC_SOF_CLIENT ) ;
2022-02-10 17:05:21 +02:00
/* module refcount management of SOF core */
int sof_client_core_module_get ( struct sof_client_dev * cdev )
{
struct snd_sof_dev * sdev = sof_client_dev_to_sof_dev ( cdev ) ;
if ( ! try_module_get ( sdev - > dev - > driver - > owner ) )
return - ENODEV ;
return 0 ;
}
EXPORT_SYMBOL_NS_GPL ( sof_client_core_module_get , SND_SOC_SOF_CLIENT ) ;
void sof_client_core_module_put ( struct sof_client_dev * cdev )
{
struct snd_sof_dev * sdev = sof_client_dev_to_sof_dev ( cdev ) ;
module_put ( sdev - > dev - > driver - > owner ) ;
}
EXPORT_SYMBOL_NS_GPL ( sof_client_core_module_put , SND_SOC_SOF_CLIENT ) ;
/* IPC event handling */
void sof_client_ipc_rx_dispatcher ( struct snd_sof_dev * sdev , void * msg_buf )
{
struct sof_ipc_event_entry * event ;
2022-05-06 16:26:44 +03:00
u32 msg_type ;
if ( sdev - > pdata - > ipc_type = = SOF_IPC ) {
struct sof_ipc_cmd_hdr * hdr = msg_buf ;
msg_type = hdr - > cmd & SOF_GLB_TYPE_MASK ;
} else if ( sdev - > pdata - > ipc_type = = SOF_INTEL_IPC4 ) {
struct sof_ipc4_msg * msg = msg_buf ;
msg_type = SOF_IPC4_NOTIFICATION_TYPE_GET ( msg - > primary ) ;
} else {
2022-06-16 16:53:48 -05:00
dev_dbg_once ( sdev - > dev , " Not supported IPC version: %d \n " ,
sdev - > pdata - > ipc_type ) ;
2022-05-06 16:26:44 +03:00
return ;
}
2022-02-10 17:05:21 +02:00
mutex_lock ( & sdev - > client_event_handler_mutex ) ;
list_for_each_entry ( event , & sdev - > ipc_rx_handler_list , list ) {
if ( event - > ipc_msg_type = = msg_type )
event - > callback ( event - > cdev , msg_buf ) ;
}
mutex_unlock ( & sdev - > client_event_handler_mutex ) ;
}
int sof_client_register_ipc_rx_handler ( struct sof_client_dev * cdev ,
u32 ipc_msg_type ,
sof_client_event_callback callback )
{
struct snd_sof_dev * sdev = sof_client_dev_to_sof_dev ( cdev ) ;
struct sof_ipc_event_entry * event ;
2022-05-06 16:26:44 +03:00
if ( ! callback )
2022-02-10 17:05:21 +02:00
return - EINVAL ;
2022-05-06 16:26:44 +03:00
if ( cdev - > sdev - > pdata - > ipc_type = = SOF_IPC ) {
if ( ! ( ipc_msg_type & SOF_GLB_TYPE_MASK ) )
return - EINVAL ;
} else if ( cdev - > sdev - > pdata - > ipc_type = = SOF_INTEL_IPC4 ) {
if ( ! ( ipc_msg_type & SOF_IPC4_NOTIFICATION_TYPE_MASK ) )
return - EINVAL ;
} else {
dev_warn ( sdev - > dev , " %s: Not supported IPC version: %d \n " ,
__func__ , sdev - > pdata - > ipc_type ) ;
return - EINVAL ;
}
2022-02-10 17:05:21 +02:00
event = kmalloc ( sizeof ( * event ) , GFP_KERNEL ) ;
if ( ! event )
return - ENOMEM ;
event - > ipc_msg_type = ipc_msg_type ;
event - > cdev = cdev ;
event - > callback = callback ;
/* add to list of SOF client devices */
mutex_lock ( & sdev - > client_event_handler_mutex ) ;
list_add ( & event - > list , & sdev - > ipc_rx_handler_list ) ;
mutex_unlock ( & sdev - > client_event_handler_mutex ) ;
return 0 ;
}
EXPORT_SYMBOL_NS_GPL ( sof_client_register_ipc_rx_handler , SND_SOC_SOF_CLIENT ) ;
void sof_client_unregister_ipc_rx_handler ( struct sof_client_dev * cdev ,
u32 ipc_msg_type )
{
struct snd_sof_dev * sdev = sof_client_dev_to_sof_dev ( cdev ) ;
struct sof_ipc_event_entry * event ;
mutex_lock ( & sdev - > client_event_handler_mutex ) ;
list_for_each_entry ( event , & sdev - > ipc_rx_handler_list , list ) {
if ( event - > cdev = = cdev & & event - > ipc_msg_type = = ipc_msg_type ) {
list_del ( & event - > list ) ;
kfree ( event ) ;
break ;
}
}
mutex_unlock ( & sdev - > client_event_handler_mutex ) ;
}
EXPORT_SYMBOL_NS_GPL ( sof_client_unregister_ipc_rx_handler , SND_SOC_SOF_CLIENT ) ;
/*DSP state notification and query */
void sof_client_fw_state_dispatcher ( struct snd_sof_dev * sdev )
{
struct sof_state_event_entry * event ;
mutex_lock ( & sdev - > client_event_handler_mutex ) ;
list_for_each_entry ( event , & sdev - > fw_state_handler_list , list )
event - > callback ( event - > cdev , sdev - > fw_state ) ;
mutex_unlock ( & sdev - > client_event_handler_mutex ) ;
}
int sof_client_register_fw_state_handler ( struct sof_client_dev * cdev ,
sof_client_fw_state_callback callback )
{
struct snd_sof_dev * sdev = sof_client_dev_to_sof_dev ( cdev ) ;
struct sof_state_event_entry * event ;
if ( ! callback )
return - EINVAL ;
event = kmalloc ( sizeof ( * event ) , GFP_KERNEL ) ;
if ( ! event )
return - ENOMEM ;
event - > cdev = cdev ;
event - > callback = callback ;
/* add to list of SOF client devices */
mutex_lock ( & sdev - > client_event_handler_mutex ) ;
list_add ( & event - > list , & sdev - > fw_state_handler_list ) ;
mutex_unlock ( & sdev - > client_event_handler_mutex ) ;
return 0 ;
}
EXPORT_SYMBOL_NS_GPL ( sof_client_register_fw_state_handler , SND_SOC_SOF_CLIENT ) ;
void sof_client_unregister_fw_state_handler ( struct sof_client_dev * cdev )
{
struct snd_sof_dev * sdev = sof_client_dev_to_sof_dev ( cdev ) ;
struct sof_state_event_entry * event ;
mutex_lock ( & sdev - > client_event_handler_mutex ) ;
list_for_each_entry ( event , & sdev - > fw_state_handler_list , list ) {
if ( event - > cdev = = cdev ) {
list_del ( & event - > list ) ;
kfree ( event ) ;
break ;
}
}
mutex_unlock ( & sdev - > client_event_handler_mutex ) ;
}
EXPORT_SYMBOL_NS_GPL ( sof_client_unregister_fw_state_handler , SND_SOC_SOF_CLIENT ) ;
enum sof_fw_state sof_client_get_fw_state ( struct sof_client_dev * cdev )
{
struct snd_sof_dev * sdev = sof_client_dev_to_sof_dev ( cdev ) ;
return sdev - > fw_state ;
}
EXPORT_SYMBOL_NS_GPL ( sof_client_get_fw_state , SND_SOC_SOF_CLIENT ) ;