2019-05-27 08:55:21 +02:00
// SPDX-License-Identifier: GPL-2.0-only
2015-06-04 21:51:47 +08:00
/*
* OPAL Runtime Diagnostics interface driver
* Supported on POWERNV platform
*
* Copyright IBM Corporation 2015
*/
# define pr_fmt(fmt) "opal-prd: " fmt
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/miscdevice.h>
# include <linux/fs.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/poll.h>
# include <linux/mm.h>
# include <linux/slab.h>
# include <asm/opal-prd.h>
# include <asm/opal.h>
# include <asm/io.h>
2016-12-24 11:46:01 -08:00
# include <linux/uaccess.h>
2015-06-04 21:51:47 +08:00
2020-08-04 10:54:08 +10:00
/*
2015-06-15 17:25:34 +08:00
* The msg member must be at the end of the struct , as it ' s followed by the
* message data .
*/
2015-06-04 21:51:47 +08:00
struct opal_prd_msg_queue_item {
struct list_head list ;
2015-06-15 17:25:34 +08:00
struct opal_prd_msg_header msg ;
2015-06-04 21:51:47 +08:00
} ;
static struct device_node * prd_node ;
static LIST_HEAD ( opal_prd_msg_queue ) ;
static DEFINE_SPINLOCK ( opal_prd_msg_queue_lock ) ;
static DECLARE_WAIT_QUEUE_HEAD ( opal_prd_msg_wait ) ;
static atomic_t prd_usage ;
static bool opal_prd_range_is_valid ( uint64_t addr , uint64_t size )
{
struct device_node * parent , * node ;
bool found ;
if ( addr + size < addr )
return false ;
parent = of_find_node_by_path ( " /reserved-memory " ) ;
if ( ! parent )
return false ;
found = false ;
for_each_child_of_node ( parent , node ) {
uint64_t range_addr , range_size , range_end ;
const __be32 * addrp ;
const char * label ;
addrp = of_get_address ( node , 0 , & range_size , NULL ) ;
range_addr = of_read_number ( addrp , 2 ) ;
range_end = range_addr + range_size ;
label = of_get_property ( node , " ibm,prd-label " , NULL ) ;
/* PRD ranges need a label */
if ( ! label )
continue ;
if ( range_end < = range_addr )
continue ;
if ( addr > = range_addr & & addr + size < = range_end ) {
found = true ;
of_node_put ( node ) ;
break ;
}
}
of_node_put ( parent ) ;
return found ;
}
static int opal_prd_open ( struct inode * inode , struct file * file )
{
/*
* Prevent multiple ( separate ) processes from concurrent interactions
* with the FW PRD channel
*/
if ( atomic_xchg ( & prd_usage , 1 ) = = 1 )
return - EBUSY ;
return 0 ;
}
/*
* opal_prd_mmap - maps firmware - provided ranges into userspace
* @ file : file structure for the device
* @ vma : VMA to map the registers into
*/
static int opal_prd_mmap ( struct file * file , struct vm_area_struct * vma )
{
size_t addr , size ;
2015-06-29 10:47:55 +05:30
pgprot_t page_prot ;
2015-06-04 21:51:47 +08:00
int rc ;
pr_devel ( " opal_prd_mmap(0x%016lx, 0x%016lx, 0x%lx, 0x%lx) \n " ,
vma - > vm_start , vma - > vm_end , vma - > vm_pgoff ,
vma - > vm_flags ) ;
addr = vma - > vm_pgoff < < PAGE_SHIFT ;
size = vma - > vm_end - vma - > vm_start ;
/* ensure we're mapping within one of the allowable ranges */
if ( ! opal_prd_range_is_valid ( addr , size ) )
return - EINVAL ;
2015-06-29 10:47:55 +05:30
page_prot = phys_mem_access_prot ( file , vma - > vm_pgoff ,
size , vma - > vm_page_prot ) ;
2015-06-04 21:51:47 +08:00
rc = remap_pfn_range ( vma , vma - > vm_start , vma - > vm_pgoff , size ,
2015-06-29 10:47:55 +05:30
page_prot ) ;
2015-06-04 21:51:47 +08:00
return rc ;
}
static bool opal_msg_queue_empty ( void )
{
unsigned long flags ;
bool ret ;
spin_lock_irqsave ( & opal_prd_msg_queue_lock , flags ) ;
ret = list_empty ( & opal_prd_msg_queue ) ;
spin_unlock_irqrestore ( & opal_prd_msg_queue_lock , flags ) ;
return ret ;
}
2017-07-02 22:33:19 -04:00
static __poll_t opal_prd_poll ( struct file * file ,
2015-06-04 21:51:47 +08:00
struct poll_table_struct * wait )
{
poll_wait ( file , & opal_prd_msg_wait , wait ) ;
if ( ! opal_msg_queue_empty ( ) )
2018-02-11 14:34:03 -08:00
return EPOLLIN | EPOLLRDNORM ;
2015-06-04 21:51:47 +08:00
return 0 ;
}
static ssize_t opal_prd_read ( struct file * file , char __user * buf ,
size_t count , loff_t * ppos )
{
struct opal_prd_msg_queue_item * item ;
unsigned long flags ;
ssize_t size , err ;
int rc ;
/* we need at least a header's worth of data */
if ( count < sizeof ( item - > msg ) )
return - EINVAL ;
if ( * ppos )
return - ESPIPE ;
item = NULL ;
for ( ; ; ) {
spin_lock_irqsave ( & opal_prd_msg_queue_lock , flags ) ;
if ( ! list_empty ( & opal_prd_msg_queue ) ) {
item = list_first_entry ( & opal_prd_msg_queue ,
struct opal_prd_msg_queue_item , list ) ;
list_del ( & item - > list ) ;
}
spin_unlock_irqrestore ( & opal_prd_msg_queue_lock , flags ) ;
if ( item )
break ;
if ( file - > f_flags & O_NONBLOCK )
return - EAGAIN ;
rc = wait_event_interruptible ( opal_prd_msg_wait ,
! opal_msg_queue_empty ( ) ) ;
if ( rc )
return - EINTR ;
}
size = be16_to_cpu ( item - > msg . size ) ;
if ( size > count ) {
err = - EINVAL ;
goto err_requeue ;
}
rc = copy_to_user ( buf , & item - > msg , size ) ;
if ( rc ) {
err = - EFAULT ;
goto err_requeue ;
}
kfree ( item ) ;
return size ;
err_requeue :
/* eep! re-queue at the head of the list */
spin_lock_irqsave ( & opal_prd_msg_queue_lock , flags ) ;
list_add ( & item - > list , & opal_prd_msg_queue ) ;
spin_unlock_irqrestore ( & opal_prd_msg_queue_lock , flags ) ;
return err ;
}
static ssize_t opal_prd_write ( struct file * file , const char __user * buf ,
size_t count , loff_t * ppos )
{
struct opal_prd_msg_header hdr ;
ssize_t size ;
void * msg ;
int rc ;
size = sizeof ( hdr ) ;
if ( count < size )
return - EINVAL ;
/* grab the header */
rc = copy_from_user ( & hdr , buf , sizeof ( hdr ) ) ;
if ( rc )
return - EFAULT ;
size = be16_to_cpu ( hdr . size ) ;
2017-04-29 09:45:14 +08:00
msg = memdup_user ( buf , size ) ;
if ( IS_ERR ( msg ) )
return PTR_ERR ( msg ) ;
2015-06-04 21:51:47 +08:00
rc = opal_prd_msg ( msg ) ;
if ( rc ) {
pr_warn ( " write: opal_prd_msg returned %d \n " , rc ) ;
size = - EIO ;
}
kfree ( msg ) ;
return size ;
}
static int opal_prd_release ( struct inode * inode , struct file * file )
{
struct opal_prd_msg_header msg ;
msg . size = cpu_to_be16 ( sizeof ( msg ) ) ;
msg . type = OPAL_PRD_MSG_TYPE_FINI ;
opal_prd_msg ( ( struct opal_prd_msg * ) & msg ) ;
atomic_xchg ( & prd_usage , 0 ) ;
return 0 ;
}
static long opal_prd_ioctl ( struct file * file , unsigned int cmd ,
unsigned long param )
{
struct opal_prd_info info ;
struct opal_prd_scom scom ;
int rc = 0 ;
switch ( cmd ) {
case OPAL_PRD_GET_INFO :
memset ( & info , 0 , sizeof ( info ) ) ;
info . version = OPAL_PRD_KERNEL_VERSION ;
rc = copy_to_user ( ( void __user * ) param , & info , sizeof ( info ) ) ;
if ( rc )
return - EFAULT ;
break ;
case OPAL_PRD_SCOM_READ :
rc = copy_from_user ( & scom , ( void __user * ) param , sizeof ( scom ) ) ;
if ( rc )
return - EFAULT ;
scom . rc = opal_xscom_read ( scom . chip , scom . addr ,
( __be64 * ) & scom . data ) ;
scom . data = be64_to_cpu ( scom . data ) ;
pr_devel ( " ioctl SCOM_READ: chip %llx addr %016llx data %016llx rc %lld \n " ,
scom . chip , scom . addr , scom . data , scom . rc ) ;
rc = copy_to_user ( ( void __user * ) param , & scom , sizeof ( scom ) ) ;
if ( rc )
return - EFAULT ;
break ;
case OPAL_PRD_SCOM_WRITE :
rc = copy_from_user ( & scom , ( void __user * ) param , sizeof ( scom ) ) ;
if ( rc )
return - EFAULT ;
scom . rc = opal_xscom_write ( scom . chip , scom . addr , scom . data ) ;
pr_devel ( " ioctl SCOM_WRITE: chip %llx addr %016llx data %016llx rc %lld \n " ,
scom . chip , scom . addr , scom . data , scom . rc ) ;
rc = copy_to_user ( ( void __user * ) param , & scom , sizeof ( scom ) ) ;
if ( rc )
return - EFAULT ;
break ;
default :
rc = - EINVAL ;
}
return rc ;
}
static const struct file_operations opal_prd_fops = {
. open = opal_prd_open ,
. mmap = opal_prd_mmap ,
. poll = opal_prd_poll ,
. read = opal_prd_read ,
. write = opal_prd_write ,
. unlocked_ioctl = opal_prd_ioctl ,
. release = opal_prd_release ,
. owner = THIS_MODULE ,
} ;
static struct miscdevice opal_prd_dev = {
. minor = MISC_DYNAMIC_MINOR ,
. name = " opal-prd " ,
. fops = & opal_prd_fops ,
} ;
/* opal interface */
static int opal_prd_msg_notifier ( struct notifier_block * nb ,
unsigned long msg_type , void * _msg )
{
struct opal_prd_msg_queue_item * item ;
struct opal_prd_msg_header * hdr ;
struct opal_msg * msg = _msg ;
2015-06-15 17:25:34 +08:00
int msg_size , item_size ;
2015-06-04 21:51:47 +08:00
unsigned long flags ;
2019-08-26 12:27:01 +05:30
if ( msg_type ! = OPAL_MSG_PRD & & msg_type ! = OPAL_MSG_PRD2 )
2015-06-04 21:51:47 +08:00
return 0 ;
2015-06-15 17:25:34 +08:00
/* Calculate total size of the message and item we need to store. The
* ' size ' field in the header includes the header itself . */
2015-06-04 21:51:47 +08:00
hdr = ( void * ) msg - > params ;
2015-06-15 17:25:34 +08:00
msg_size = be16_to_cpu ( hdr - > size ) ;
item_size = msg_size + sizeof ( * item ) - sizeof ( item - > msg ) ;
2015-06-04 21:51:47 +08:00
2015-06-15 17:25:34 +08:00
item = kzalloc ( item_size , GFP_ATOMIC ) ;
2015-06-04 21:51:47 +08:00
if ( ! item )
return - ENOMEM ;
2015-06-15 17:25:34 +08:00
memcpy ( & item - > msg , msg - > params , msg_size ) ;
2015-06-04 21:51:47 +08:00
spin_lock_irqsave ( & opal_prd_msg_queue_lock , flags ) ;
list_add_tail ( & item - > list , & opal_prd_msg_queue ) ;
spin_unlock_irqrestore ( & opal_prd_msg_queue_lock , flags ) ;
wake_up_interruptible ( & opal_prd_msg_wait ) ;
return 0 ;
}
static struct notifier_block opal_prd_event_nb = {
. notifier_call = opal_prd_msg_notifier ,
. next = NULL ,
. priority = 0 ,
} ;
static int opal_prd_probe ( struct platform_device * pdev )
{
int rc ;
if ( ! pdev | | ! pdev - > dev . of_node )
return - ENODEV ;
/* We should only have one prd driver instance per machine; ensure
* that we only get a valid probe on a single OF node .
*/
if ( prd_node )
return - EBUSY ;
prd_node = pdev - > dev . of_node ;
rc = opal_message_notifier_register ( OPAL_MSG_PRD , & opal_prd_event_nb ) ;
if ( rc ) {
pr_err ( " Couldn't register event notifier \n " ) ;
return rc ;
}
2019-08-26 12:27:01 +05:30
rc = opal_message_notifier_register ( OPAL_MSG_PRD2 , & opal_prd_event_nb ) ;
if ( rc ) {
pr_err ( " Couldn't register PRD2 event notifier \n " ) ;
return rc ;
}
2015-06-04 21:51:47 +08:00
rc = misc_register ( & opal_prd_dev ) ;
if ( rc ) {
pr_err ( " failed to register miscdev \n " ) ;
opal_message_notifier_unregister ( OPAL_MSG_PRD ,
& opal_prd_event_nb ) ;
return rc ;
}
return 0 ;
}
static int opal_prd_remove ( struct platform_device * pdev )
{
misc_deregister ( & opal_prd_dev ) ;
opal_message_notifier_unregister ( OPAL_MSG_PRD , & opal_prd_event_nb ) ;
return 0 ;
}
static const struct of_device_id opal_prd_match [ ] = {
{ . compatible = " ibm,opal-prd " } ,
{ } ,
} ;
static struct platform_driver opal_prd_driver = {
. driver = {
. name = " opal-prd " ,
. of_match_table = opal_prd_match ,
} ,
. probe = opal_prd_probe ,
. remove = opal_prd_remove ,
} ;
module_platform_driver ( opal_prd_driver ) ;
MODULE_DEVICE_TABLE ( of , opal_prd_match ) ;
MODULE_DESCRIPTION ( " PowerNV OPAL runtime diagnostic driver " ) ;
MODULE_LICENSE ( " GPL " ) ;