2014-03-07 11:00:24 +05:30
/*
* PowerNV OPAL asynchronous completion interfaces
*
* Copyright 2013 IBM Corp .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*/
# undef DEBUG
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/sched.h>
# include <linux/semaphore.h>
# include <linux/spinlock.h>
# include <linux/wait.h>
# include <linux/gfp.h>
# include <linux/of.h>
# include <asm/opal.h>
# define N_ASYNC_COMPLETIONS 64
static DECLARE_BITMAP ( opal_async_complete_map , N_ASYNC_COMPLETIONS ) = { ~ 0UL } ;
static DECLARE_BITMAP ( opal_async_token_map , N_ASYNC_COMPLETIONS ) ;
static DECLARE_WAIT_QUEUE_HEAD ( opal_async_wait ) ;
static DEFINE_SPINLOCK ( opal_async_comp_lock ) ;
static struct semaphore opal_async_sem ;
static struct opal_msg * opal_async_responses ;
static unsigned int opal_max_async_tokens ;
int __opal_async_get_token ( void )
{
unsigned long flags ;
int token ;
spin_lock_irqsave ( & opal_async_comp_lock , flags ) ;
token = find_first_bit ( opal_async_complete_map , opal_max_async_tokens ) ;
if ( token > = opal_max_async_tokens ) {
token = - EBUSY ;
goto out ;
}
if ( __test_and_set_bit ( token , opal_async_token_map ) ) {
token = - EBUSY ;
goto out ;
}
__clear_bit ( token , opal_async_complete_map ) ;
out :
spin_unlock_irqrestore ( & opal_async_comp_lock , flags ) ;
return token ;
}
int opal_async_get_token_interruptible ( void )
{
int token ;
/* Wait until a token is available */
if ( down_interruptible ( & opal_async_sem ) )
return - ERESTARTSYS ;
token = __opal_async_get_token ( ) ;
if ( token < 0 )
up ( & opal_async_sem ) ;
return token ;
}
int __opal_async_release_token ( int token )
{
unsigned long flags ;
if ( token < 0 | | token > = opal_max_async_tokens ) {
pr_err ( " %s: Passed token is out of range, token %d \n " ,
__func__ , token ) ;
return - EINVAL ;
}
spin_lock_irqsave ( & opal_async_comp_lock , flags ) ;
__set_bit ( token , opal_async_complete_map ) ;
__clear_bit ( token , opal_async_token_map ) ;
spin_unlock_irqrestore ( & opal_async_comp_lock , flags ) ;
return 0 ;
}
int opal_async_release_token ( int token )
{
int ret ;
ret = __opal_async_release_token ( token ) ;
if ( ret )
return ret ;
up ( & opal_async_sem ) ;
return 0 ;
}
int opal_async_wait_response ( uint64_t token , struct opal_msg * msg )
{
if ( token > = opal_max_async_tokens ) {
pr_err ( " %s: Invalid token passed \n " , __func__ ) ;
return - EINVAL ;
}
if ( ! msg ) {
pr_err ( " %s: Invalid message pointer passed \n " , __func__ ) ;
return - EINVAL ;
}
wait_event ( opal_async_wait , test_bit ( token , opal_async_complete_map ) ) ;
memcpy ( msg , & opal_async_responses [ token ] , sizeof ( * msg ) ) ;
return 0 ;
}
static int opal_async_comp_event ( struct notifier_block * nb ,
unsigned long msg_type , void * msg )
{
struct opal_msg * comp_msg = msg ;
unsigned long flags ;
2014-03-28 16:33:33 +11:00
uint64_t token ;
2014-03-07 11:00:24 +05:30
if ( msg_type ! = OPAL_MSG_ASYNC_COMP )
return 0 ;
2014-03-28 16:33:33 +11:00
token = be64_to_cpu ( comp_msg - > params [ 0 ] ) ;
memcpy ( & opal_async_responses [ token ] , comp_msg , sizeof ( * comp_msg ) ) ;
2014-03-07 11:00:24 +05:30
spin_lock_irqsave ( & opal_async_comp_lock , flags ) ;
2014-03-28 16:33:33 +11:00
__set_bit ( token , opal_async_complete_map ) ;
2014-03-07 11:00:24 +05:30
spin_unlock_irqrestore ( & opal_async_comp_lock , flags ) ;
wake_up ( & opal_async_wait ) ;
return 0 ;
}
static struct notifier_block opal_async_comp_nb = {
. notifier_call = opal_async_comp_event ,
. next = NULL ,
. priority = 0 ,
} ;
static int __init opal_async_comp_init ( void )
{
struct device_node * opal_node ;
const __be32 * async ;
int err ;
opal_node = of_find_node_by_path ( " /ibm,opal " ) ;
if ( ! opal_node ) {
pr_err ( " %s: Opal node not found \n " , __func__ ) ;
err = - ENOENT ;
goto out ;
}
async = of_get_property ( opal_node , " opal-msg-async-num " , NULL ) ;
if ( ! async ) {
pr_err ( " %s: %s has no opal-msg-async-num \n " ,
__func__ , opal_node - > full_name ) ;
err = - ENOENT ;
goto out_opal_node ;
}
opal_max_async_tokens = be32_to_cpup ( async ) ;
if ( opal_max_async_tokens > N_ASYNC_COMPLETIONS )
opal_max_async_tokens = N_ASYNC_COMPLETIONS ;
err = opal_message_notifier_register ( OPAL_MSG_ASYNC_COMP ,
& opal_async_comp_nb ) ;
if ( err ) {
pr_err ( " %s: Can't register OPAL event notifier (%d) \n " ,
__func__ , err ) ;
goto out_opal_node ;
}
opal_async_responses = kzalloc (
sizeof ( * opal_async_responses ) * opal_max_async_tokens ,
GFP_KERNEL ) ;
if ( ! opal_async_responses ) {
pr_err ( " %s: Out of memory, failed to do asynchronous "
" completion init \n " , __func__ ) ;
err = - ENOMEM ;
goto out_opal_node ;
}
/* Initialize to 1 less than the maximum tokens available, as we may
* require to pop one during emergency through synchronous call to
* __opal_async_get_token ( )
*/
sema_init ( & opal_async_sem , opal_max_async_tokens - 1 ) ;
out_opal_node :
of_node_put ( opal_node ) ;
out :
return err ;
}
subsys_initcall ( opal_async_comp_init ) ;