2019-05-27 08:55:21 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2017-01-27 02:28:32 -08:00
/*
* Qualcomm Peripheral Image Loader helpers
*
* Copyright ( C ) 2016 Linaro Ltd
* Copyright ( C ) 2015 Sony Mobile Communications Inc
* Copyright ( c ) 2012 - 2013 , The Linux Foundation . All rights reserved .
*/
# include <linux/firmware.h>
# include <linux/kernel.h>
# include <linux/module.h>
2017-07-24 22:56:43 -07:00
# include <linux/notifier.h>
2017-01-27 02:28:32 -08:00
# include <linux/remoteproc.h>
2020-06-23 19:23:27 -07:00
# include <linux/remoteproc/qcom_rproc.h>
2017-08-29 16:13:35 -07:00
# include <linux/rpmsg/qcom_glink.h>
2017-01-27 07:04:54 -08:00
# include <linux/rpmsg/qcom_smd.h>
2020-07-13 10:00:03 +08:00
# include <linux/slab.h>
2018-01-05 16:04:20 -08:00
# include <linux/soc/qcom/mdt_loader.h>
2020-11-19 13:05:34 -08:00
# include <linux/soc/qcom/smem.h>
2017-01-27 02:28:32 -08:00
# include "remoteproc_internal.h"
# include "qcom_common.h"
2017-08-29 16:13:35 -07:00
# define to_glink_subdev(d) container_of(d, struct qcom_rproc_glink, subdev)
2017-01-27 07:04:54 -08:00
# define to_smd_subdev(d) container_of(d, struct qcom_rproc_subdev, subdev)
2017-07-24 22:56:43 -07:00
# define to_ssr_subdev(d) container_of(d, struct qcom_rproc_ssr, subdev)
2020-11-19 13:05:34 -08:00
# define MAX_NUM_OF_SS 10
# define MAX_REGION_NAME_LENGTH 16
# define SBL_MINIDUMP_SMEM_ID 602
# define MD_REGION_VALID ('V' << 24 | 'A' << 16 | 'L' << 8 | 'I' << 0)
# define MD_SS_ENCR_DONE ('D' << 24 | 'O' << 16 | 'N' << 8 | 'E' << 0)
# define MD_SS_ENABLED ('E' << 24 | 'N' << 16 | 'B' << 8 | 'L' << 0)
/**
* struct minidump_region - Minidump region
* @ name : Name of the region to be dumped
* @ seq_num : : Use to differentiate regions with same name .
* @ valid : This entry to be dumped ( if set to 1 )
* @ address : Physical address of region to be dumped
* @ size : Size of the region
*/
struct minidump_region {
char name [ MAX_REGION_NAME_LENGTH ] ;
__le32 seq_num ;
__le32 valid ;
__le64 address ;
__le64 size ;
} ;
/**
* struct minidump_subsystem_toc : Subsystem ' s SMEM Table of content
* @ status : Subsystem toc init status
* @ enabled : if set to 1 , this region would be copied during coredump
* @ encryption_status : Encryption status for this subsystem
* @ encryption_required : Decides to encrypt the subsystem regions or not
* @ region_count : Number of regions added in this subsystem toc
* @ regions_baseptr : regions base pointer of the subsystem
*/
struct minidump_subsystem {
__le32 status ;
__le32 enabled ;
__le32 encryption_status ;
__le32 encryption_required ;
__le32 region_count ;
__le64 regions_baseptr ;
} ;
/**
* struct minidump_global_toc : Global Table of Content
* @ status : Global Minidump init status
* @ md_revision : Minidump revision
* @ enabled : Minidump enable status
* @ subsystems : Array of subsystems toc
*/
struct minidump_global_toc {
__le32 status ;
__le32 md_revision ;
__le32 enabled ;
struct minidump_subsystem subsystems [ MAX_NUM_OF_SS ] ;
} ;
2020-06-23 19:23:27 -07:00
struct qcom_ssr_subsystem {
const char * name ;
struct srcu_notifier_head notifier_list ;
struct list_head list ;
} ;
static LIST_HEAD ( qcom_ssr_subsystem_list ) ;
static DEFINE_MUTEX ( qcom_ssr_subsys_lock ) ;
2017-01-27 07:04:54 -08:00
2020-11-19 13:05:34 -08:00
static void qcom_minidump_cleanup ( struct rproc * rproc )
{
struct rproc_dump_segment * entry , * tmp ;
list_for_each_entry_safe ( entry , tmp , & rproc - > dump_segments , node ) {
list_del ( & entry - > node ) ;
kfree ( entry - > priv ) ;
kfree ( entry ) ;
}
}
static int qcom_add_minidump_segments ( struct rproc * rproc , struct minidump_subsystem * subsystem )
{
struct minidump_region __iomem * ptr ;
struct minidump_region region ;
int seg_cnt , i ;
dma_addr_t da ;
size_t size ;
char * name ;
if ( WARN_ON ( ! list_empty ( & rproc - > dump_segments ) ) ) {
dev_err ( & rproc - > dev , " dump segment list already populated \n " ) ;
return - EUCLEAN ;
}
seg_cnt = le32_to_cpu ( subsystem - > region_count ) ;
ptr = ioremap ( ( unsigned long ) le64_to_cpu ( subsystem - > regions_baseptr ) ,
seg_cnt * sizeof ( struct minidump_region ) ) ;
if ( ! ptr )
return - EFAULT ;
for ( i = 0 ; i < seg_cnt ; i + + ) {
memcpy_fromio ( & region , ptr + i , sizeof ( region ) ) ;
if ( region . valid = = MD_REGION_VALID ) {
name = kstrdup ( region . name , GFP_KERNEL ) ;
if ( ! name ) {
iounmap ( ptr ) ;
return - ENOMEM ;
}
da = le64_to_cpu ( region . address ) ;
size = le32_to_cpu ( region . size ) ;
rproc_coredump_add_custom_segment ( rproc , da , size , NULL , name ) ;
}
}
iounmap ( ptr ) ;
return 0 ;
}
void qcom_minidump ( struct rproc * rproc , unsigned int minidump_id )
{
int ret ;
struct minidump_subsystem * subsystem ;
struct minidump_global_toc * toc ;
/* Get Global minidump ToC*/
toc = qcom_smem_get ( QCOM_SMEM_HOST_ANY , SBL_MINIDUMP_SMEM_ID , NULL ) ;
/* check if global table pointer exists and init is set */
if ( IS_ERR ( toc ) | | ! toc - > status ) {
dev_err ( & rproc - > dev , " Minidump TOC not found in SMEM \n " ) ;
return ;
}
/* Get subsystem table of contents using the minidump id */
subsystem = & toc - > subsystems [ minidump_id ] ;
/**
* Collect minidump if SS ToC is valid and segment table
* is initialized in memory and encryption status is set .
*/
if ( subsystem - > regions_baseptr = = 0 | |
le32_to_cpu ( subsystem - > status ) ! = 1 | |
le32_to_cpu ( subsystem - > enabled ) ! = MD_SS_ENABLED | |
le32_to_cpu ( subsystem - > encryption_status ) ! = MD_SS_ENCR_DONE ) {
dev_err ( & rproc - > dev , " Minidump not ready, skipping \n " ) ;
return ;
}
ret = qcom_add_minidump_segments ( rproc , subsystem ) ;
if ( ret ) {
dev_err ( & rproc - > dev , " Failed with error: %d while adding minidump entries \n " , ret ) ;
goto clean_minidump ;
}
rproc_coredump_using_sections ( rproc ) ;
clean_minidump :
qcom_minidump_cleanup ( rproc ) ;
}
EXPORT_SYMBOL_GPL ( qcom_minidump ) ;
2018-06-26 07:11:58 -05:00
static int glink_subdev_start ( struct rproc_subdev * subdev )
2017-08-29 16:13:35 -07:00
{
struct qcom_rproc_glink * glink = to_glink_subdev ( subdev ) ;
glink - > edge = qcom_glink_smem_register ( glink - > dev , glink - > node ) ;
2017-11-28 23:32:40 +01:00
return PTR_ERR_OR_ZERO ( glink - > edge ) ;
2017-08-29 16:13:35 -07:00
}
2018-06-26 07:11:58 -05:00
static void glink_subdev_stop ( struct rproc_subdev * subdev , bool crashed )
2017-08-29 16:13:35 -07:00
{
struct qcom_rproc_glink * glink = to_glink_subdev ( subdev ) ;
qcom_glink_smem_unregister ( glink - > edge ) ;
glink - > edge = NULL ;
}
2020-04-22 17:37:34 -07:00
static void glink_subdev_unprepare ( struct rproc_subdev * subdev )
{
struct qcom_rproc_glink * glink = to_glink_subdev ( subdev ) ;
qcom_glink_ssr_notify ( glink - > ssr_name ) ;
}
2017-08-29 16:13:35 -07:00
/**
* qcom_add_glink_subdev ( ) - try to add a GLINK subdevice to rproc
* @ rproc : rproc handle to parent the subdevice
* @ glink : reference to a GLINK subdev context
2020-04-22 17:37:33 -07:00
* @ ssr_name : identifier of the associated remoteproc for ssr notifications
2017-08-29 16:13:35 -07:00
*/
2020-04-22 17:37:33 -07:00
void qcom_add_glink_subdev ( struct rproc * rproc , struct qcom_rproc_glink * glink ,
const char * ssr_name )
2017-08-29 16:13:35 -07:00
{
struct device * dev = & rproc - > dev ;
glink - > node = of_get_child_by_name ( dev - > parent - > of_node , " glink-edge " ) ;
if ( ! glink - > node )
return ;
2020-04-22 17:37:33 -07:00
glink - > ssr_name = kstrdup_const ( ssr_name , GFP_KERNEL ) ;
if ( ! glink - > ssr_name )
return ;
2017-08-29 16:13:35 -07:00
glink - > dev = dev ;
2018-06-26 07:11:58 -05:00
glink - > subdev . start = glink_subdev_start ;
glink - > subdev . stop = glink_subdev_stop ;
2020-04-22 17:37:34 -07:00
glink - > subdev . unprepare = glink_subdev_unprepare ;
2018-06-26 07:11:57 -05:00
rproc_add_subdev ( rproc , & glink - > subdev ) ;
2017-08-29 16:13:35 -07:00
}
EXPORT_SYMBOL_GPL ( qcom_add_glink_subdev ) ;
/**
* qcom_remove_glink_subdev ( ) - remove a GLINK subdevice from rproc
* @ rproc : rproc handle
* @ glink : reference to a GLINK subdev context
*/
void qcom_remove_glink_subdev ( struct rproc * rproc , struct qcom_rproc_glink * glink )
{
2018-04-03 23:45:15 +05:30
if ( ! glink - > node )
return ;
2017-08-29 16:13:35 -07:00
rproc_remove_subdev ( rproc , & glink - > subdev ) ;
2020-04-22 17:37:33 -07:00
kfree_const ( glink - > ssr_name ) ;
2017-08-29 16:13:35 -07:00
of_node_put ( glink - > node ) ;
}
EXPORT_SYMBOL_GPL ( qcom_remove_glink_subdev ) ;
2018-01-05 16:04:20 -08:00
/**
* qcom_register_dump_segments ( ) - register segments for coredump
* @ rproc : remoteproc handle
* @ fw : firmware header
*
* Register all segments of the ELF in the remoteproc coredump segment list
*
* Return : 0 on success , negative errno on failure .
*/
int qcom_register_dump_segments ( struct rproc * rproc ,
const struct firmware * fw )
{
const struct elf32_phdr * phdrs ;
const struct elf32_phdr * phdr ;
const struct elf32_hdr * ehdr ;
int ret ;
int i ;
ehdr = ( struct elf32_hdr * ) fw - > data ;
phdrs = ( struct elf32_phdr * ) ( ehdr + 1 ) ;
for ( i = 0 ; i < ehdr - > e_phnum ; i + + ) {
phdr = & phdrs [ i ] ;
if ( phdr - > p_type ! = PT_LOAD )
continue ;
if ( ( phdr - > p_flags & QCOM_MDT_TYPE_MASK ) = = QCOM_MDT_TYPE_HASH )
continue ;
if ( ! phdr - > p_memsz )
continue ;
ret = rproc_coredump_add_segment ( rproc , phdr - > p_paddr ,
phdr - > p_memsz ) ;
if ( ret )
return ret ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( qcom_register_dump_segments ) ;
2018-06-26 07:11:58 -05:00
static int smd_subdev_start ( struct rproc_subdev * subdev )
2017-01-27 07:04:54 -08:00
{
struct qcom_rproc_subdev * smd = to_smd_subdev ( subdev ) ;
smd - > edge = qcom_smd_register_edge ( smd - > dev , smd - > node ) ;
2017-08-29 19:13:18 +05:30
return PTR_ERR_OR_ZERO ( smd - > edge ) ;
2017-01-27 07:04:54 -08:00
}
2018-06-26 07:11:58 -05:00
static void smd_subdev_stop ( struct rproc_subdev * subdev , bool crashed )
2017-01-27 07:04:54 -08:00
{
struct qcom_rproc_subdev * smd = to_smd_subdev ( subdev ) ;
qcom_smd_unregister_edge ( smd - > edge ) ;
smd - > edge = NULL ;
}
/**
* qcom_add_smd_subdev ( ) - try to add a SMD subdevice to rproc
* @ rproc : rproc handle to parent the subdevice
* @ smd : reference to a Qualcomm subdev context
*/
void qcom_add_smd_subdev ( struct rproc * rproc , struct qcom_rproc_subdev * smd )
{
struct device * dev = & rproc - > dev ;
smd - > node = of_get_child_by_name ( dev - > parent - > of_node , " smd-edge " ) ;
if ( ! smd - > node )
return ;
smd - > dev = dev ;
2018-06-26 07:11:58 -05:00
smd - > subdev . start = smd_subdev_start ;
smd - > subdev . stop = smd_subdev_stop ;
2018-06-26 07:11:57 -05:00
rproc_add_subdev ( rproc , & smd - > subdev ) ;
2017-01-27 07:04:54 -08:00
}
EXPORT_SYMBOL_GPL ( qcom_add_smd_subdev ) ;
/**
* qcom_remove_smd_subdev ( ) - remove the smd subdevice from rproc
* @ rproc : rproc handle
* @ smd : the SMD subdevice to remove
*/
void qcom_remove_smd_subdev ( struct rproc * rproc , struct qcom_rproc_subdev * smd )
{
2018-04-03 23:45:15 +05:30
if ( ! smd - > node )
return ;
2017-01-27 07:04:54 -08:00
rproc_remove_subdev ( rproc , & smd - > subdev ) ;
of_node_put ( smd - > node ) ;
}
EXPORT_SYMBOL_GPL ( qcom_remove_smd_subdev ) ;
2020-06-23 19:23:27 -07:00
static struct qcom_ssr_subsystem * qcom_ssr_get_subsys ( const char * name )
{
struct qcom_ssr_subsystem * info ;
mutex_lock ( & qcom_ssr_subsys_lock ) ;
/* Match in the global qcom_ssr_subsystem_list with name */
list_for_each_entry ( info , & qcom_ssr_subsystem_list , list )
if ( ! strcmp ( info - > name , name ) )
goto out ;
info = kzalloc ( sizeof ( * info ) , GFP_KERNEL ) ;
if ( ! info ) {
info = ERR_PTR ( - ENOMEM ) ;
goto out ;
}
info - > name = kstrdup_const ( name , GFP_KERNEL ) ;
srcu_init_notifier_head ( & info - > notifier_list ) ;
/* Add to global notification list */
list_add_tail ( & info - > list , & qcom_ssr_subsystem_list ) ;
out :
mutex_unlock ( & qcom_ssr_subsys_lock ) ;
return info ;
}
2017-07-24 22:56:43 -07:00
/**
* qcom_register_ssr_notifier ( ) - register SSR notification handler
2020-06-23 19:23:27 -07:00
* @ name : Subsystem ' s SSR name
* @ nb : notifier_block to be invoked upon subsystem ' s state change
2017-07-24 22:56:43 -07:00
*
2020-06-23 19:23:27 -07:00
* This registers the @ nb notifier block as part the notifier chain for a
* remoteproc associated with @ name . The notifier block ' s callback
* will be invoked when the remote processor ' s SSR events occur
* ( pre / post startup and pre / post shutdown ) .
2017-07-24 22:56:43 -07:00
*
2020-06-23 19:23:27 -07:00
* Return : a subsystem cookie on success , ERR_PTR on failure .
2017-07-24 22:56:43 -07:00
*/
2020-06-23 19:23:27 -07:00
void * qcom_register_ssr_notifier ( const char * name , struct notifier_block * nb )
2017-07-24 22:56:43 -07:00
{
2020-06-23 19:23:27 -07:00
struct qcom_ssr_subsystem * info ;
info = qcom_ssr_get_subsys ( name ) ;
if ( IS_ERR ( info ) )
return info ;
srcu_notifier_chain_register ( & info - > notifier_list , nb ) ;
return & info - > notifier_list ;
2017-07-24 22:56:43 -07:00
}
EXPORT_SYMBOL_GPL ( qcom_register_ssr_notifier ) ;
/**
* qcom_unregister_ssr_notifier ( ) - unregister SSR notification handler
2020-06-23 19:23:27 -07:00
* @ notify : subsystem cookie returned from qcom_register_ssr_notifier
2017-07-24 22:56:43 -07:00
* @ nb : notifier_block to unregister
2020-06-23 19:23:27 -07:00
*
* This function will unregister the notifier from the particular notifier
* chain .
*
* Return : 0 on success , % ENOENT otherwise .
2017-07-24 22:56:43 -07:00
*/
2020-06-23 19:23:27 -07:00
int qcom_unregister_ssr_notifier ( void * notify , struct notifier_block * nb )
2017-07-24 22:56:43 -07:00
{
2020-06-23 19:23:27 -07:00
return srcu_notifier_chain_unregister ( notify , nb ) ;
2017-07-24 22:56:43 -07:00
}
EXPORT_SYMBOL_GPL ( qcom_unregister_ssr_notifier ) ;
2020-06-23 19:23:28 -07:00
static int ssr_notify_prepare ( struct rproc_subdev * subdev )
{
struct qcom_rproc_ssr * ssr = to_ssr_subdev ( subdev ) ;
struct qcom_ssr_notify_data data = {
. name = ssr - > info - > name ,
. crashed = false ,
} ;
srcu_notifier_call_chain ( & ssr - > info - > notifier_list ,
QCOM_SSR_BEFORE_POWERUP , & data ) ;
return 0 ;
}
static int ssr_notify_start ( struct rproc_subdev * subdev )
{
struct qcom_rproc_ssr * ssr = to_ssr_subdev ( subdev ) ;
struct qcom_ssr_notify_data data = {
. name = ssr - > info - > name ,
. crashed = false ,
} ;
srcu_notifier_call_chain ( & ssr - > info - > notifier_list ,
QCOM_SSR_AFTER_POWERUP , & data ) ;
return 0 ;
}
static void ssr_notify_stop ( struct rproc_subdev * subdev , bool crashed )
{
struct qcom_rproc_ssr * ssr = to_ssr_subdev ( subdev ) ;
struct qcom_ssr_notify_data data = {
. name = ssr - > info - > name ,
. crashed = crashed ,
} ;
srcu_notifier_call_chain ( & ssr - > info - > notifier_list ,
QCOM_SSR_BEFORE_SHUTDOWN , & data ) ;
}
2019-07-15 22:03:27 -07:00
static void ssr_notify_unprepare ( struct rproc_subdev * subdev )
2017-07-24 22:56:43 -07:00
{
struct qcom_rproc_ssr * ssr = to_ssr_subdev ( subdev ) ;
2020-06-23 19:23:27 -07:00
struct qcom_ssr_notify_data data = {
. name = ssr - > info - > name ,
. crashed = false ,
} ;
2017-07-24 22:56:43 -07:00
2020-06-23 19:23:28 -07:00
srcu_notifier_call_chain ( & ssr - > info - > notifier_list ,
QCOM_SSR_AFTER_SHUTDOWN , & data ) ;
2017-07-24 22:56:43 -07:00
}
/**
* qcom_add_ssr_subdev ( ) - register subdevice as restart notification source
* @ rproc : rproc handle
* @ ssr : SSR subdevice handle
* @ ssr_name : identifier to use for notifications originating from @ rproc
*
* As the @ ssr is registered with the @ rproc SSR events will be sent to all
2020-06-23 19:23:27 -07:00
* registered listeners for the remoteproc when it ' s SSR events occur
* ( pre / post startup and pre / post shutdown ) .
2017-07-24 22:56:43 -07:00
*/
void qcom_add_ssr_subdev ( struct rproc * rproc , struct qcom_rproc_ssr * ssr ,
const char * ssr_name )
{
2020-06-23 19:23:27 -07:00
struct qcom_ssr_subsystem * info ;
info = qcom_ssr_get_subsys ( ssr_name ) ;
if ( IS_ERR ( info ) ) {
dev_err ( & rproc - > dev , " Failed to add ssr subdevice \n " ) ;
return ;
}
ssr - > info = info ;
2020-06-23 19:23:28 -07:00
ssr - > subdev . prepare = ssr_notify_prepare ;
ssr - > subdev . start = ssr_notify_start ;
ssr - > subdev . stop = ssr_notify_stop ;
2019-07-15 22:03:27 -07:00
ssr - > subdev . unprepare = ssr_notify_unprepare ;
2017-07-24 22:56:43 -07:00
2018-06-26 07:11:57 -05:00
rproc_add_subdev ( rproc , & ssr - > subdev ) ;
2017-07-24 22:56:43 -07:00
}
EXPORT_SYMBOL_GPL ( qcom_add_ssr_subdev ) ;
/**
* qcom_remove_ssr_subdev ( ) - remove subdevice as restart notification source
* @ rproc : rproc handle
* @ ssr : SSR subdevice handle
*/
void qcom_remove_ssr_subdev ( struct rproc * rproc , struct qcom_rproc_ssr * ssr )
{
rproc_remove_subdev ( rproc , & ssr - > subdev ) ;
2020-06-23 19:23:27 -07:00
ssr - > info = NULL ;
2017-07-24 22:56:43 -07:00
}
EXPORT_SYMBOL_GPL ( qcom_remove_ssr_subdev ) ;
2017-01-27 02:28:32 -08:00
MODULE_DESCRIPTION ( " Qualcomm Remoteproc helper driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;