2011-06-10 00:52:06 +04:00
/*
* Freescale Hypervisor Management Driver
* Copyright ( C ) 2008 - 2011 Freescale Semiconductor , Inc .
* Author : Timur Tabi < timur @ freescale . com >
*
* This file is licensed under the terms of the GNU General Public License
* version 2. This program is licensed " as is " without any warranty of any
* kind , whether express or implied .
*
* The Freescale hypervisor management driver provides several services to
* drivers and applications related to the Freescale hypervisor :
*
* 1. An ioctl interface for querying and managing partitions .
*
* 2. A file interface to reading incoming doorbells .
*
* 3. An interrupt handler for shutting down the partition upon receiving the
* shutdown doorbell from a manager partition .
*
* 4. A kernel interface for receiving callbacks when a managed partition
* shuts down .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/err.h>
# include <linux/fs.h>
# include <linux/miscdevice.h>
# include <linux/mm.h>
# include <linux/pagemap.h>
# include <linux/slab.h>
# include <linux/poll.h>
# include <linux/of.h>
2013-09-17 23:28:33 +04:00
# include <linux/of_irq.h>
2011-06-10 00:52:06 +04:00
# include <linux/reboot.h>
# include <linux/uaccess.h>
# include <linux/notifier.h>
2011-07-20 00:45:51 +04:00
# include <linux/interrupt.h>
2011-06-10 00:52:06 +04:00
# include <linux/io.h>
# include <asm/fsl_hcalls.h>
# include <linux/fsl_hypervisor.h>
static BLOCKING_NOTIFIER_HEAD ( failover_subscribers ) ;
/*
* Ioctl interface for FSL_HV_IOCTL_PARTITION_RESTART
*
* Restart a running partition
*/
static long ioctl_restart ( struct fsl_hv_ioctl_restart __user * p )
{
struct fsl_hv_ioctl_restart param ;
/* Get the parameters from the user */
if ( copy_from_user ( & param , p , sizeof ( struct fsl_hv_ioctl_restart ) ) )
return - EFAULT ;
param . ret = fh_partition_restart ( param . partition ) ;
if ( copy_to_user ( & p - > ret , & param . ret , sizeof ( __u32 ) ) )
return - EFAULT ;
return 0 ;
}
/*
* Ioctl interface for FSL_HV_IOCTL_PARTITION_STATUS
*
* Query the status of a partition
*/
static long ioctl_status ( struct fsl_hv_ioctl_status __user * p )
{
struct fsl_hv_ioctl_status param ;
u32 status ;
/* Get the parameters from the user */
if ( copy_from_user ( & param , p , sizeof ( struct fsl_hv_ioctl_status ) ) )
return - EFAULT ;
param . ret = fh_partition_get_status ( param . partition , & status ) ;
if ( ! param . ret )
param . status = status ;
if ( copy_to_user ( p , & param , sizeof ( struct fsl_hv_ioctl_status ) ) )
return - EFAULT ;
return 0 ;
}
/*
* Ioctl interface for FSL_HV_IOCTL_PARTITION_START
*
* Start a stopped partition .
*/
static long ioctl_start ( struct fsl_hv_ioctl_start __user * p )
{
struct fsl_hv_ioctl_start param ;
/* Get the parameters from the user */
if ( copy_from_user ( & param , p , sizeof ( struct fsl_hv_ioctl_start ) ) )
return - EFAULT ;
param . ret = fh_partition_start ( param . partition , param . entry_point ,
param . load ) ;
if ( copy_to_user ( & p - > ret , & param . ret , sizeof ( __u32 ) ) )
return - EFAULT ;
return 0 ;
}
/*
* Ioctl interface for FSL_HV_IOCTL_PARTITION_STOP
*
* Stop a running partition
*/
static long ioctl_stop ( struct fsl_hv_ioctl_stop __user * p )
{
struct fsl_hv_ioctl_stop param ;
/* Get the parameters from the user */
if ( copy_from_user ( & param , p , sizeof ( struct fsl_hv_ioctl_stop ) ) )
return - EFAULT ;
param . ret = fh_partition_stop ( param . partition ) ;
if ( copy_to_user ( & p - > ret , & param . ret , sizeof ( __u32 ) ) )
return - EFAULT ;
return 0 ;
}
/*
* Ioctl interface for FSL_HV_IOCTL_MEMCPY
*
* The FH_MEMCPY hypercall takes an array of address / address / size structures
* to represent the data being copied . As a convenience to the user , this
* ioctl takes a user - create buffer and a pointer to a guest physically
* contiguous buffer in the remote partition , and creates the
* address / address / size array for the hypercall .
*/
static long ioctl_memcpy ( struct fsl_hv_ioctl_memcpy __user * p )
{
struct fsl_hv_ioctl_memcpy param ;
struct page * * pages = NULL ;
void * sg_list_unaligned = NULL ;
struct fh_sg_list * sg_list = NULL ;
unsigned int num_pages ;
unsigned long lb_offset ; /* Offset within a page of the local buffer */
unsigned int i ;
long ret = 0 ;
2020-09-02 00:21:11 +03:00
int num_pinned = 0 ; /* return value from get_user_pages_fast() */
2011-06-10 00:52:06 +04:00
phys_addr_t remote_paddr ; /* The next address in the remote buffer */
uint32_t count ; /* The number of bytes left to copy */
/* Get the parameters from the user */
if ( copy_from_user ( & param , p , sizeof ( struct fsl_hv_ioctl_memcpy ) ) )
return - EFAULT ;
/*
* One partition must be local , the other must be remote . In other
* words , if source and target are both - 1 , or are both not - 1 , then
* return an error .
*/
if ( ( param . source = = - 1 ) = = ( param . target = = - 1 ) )
return - EINVAL ;
/*
2020-09-02 00:21:11 +03:00
* The array of pages returned by get_user_pages_fast ( ) covers only
2011-06-10 00:52:06 +04:00
* page - aligned memory . Since the user buffer is probably not
* page - aligned , we need to handle the discrepancy .
*
* We calculate the offset within a page of the S / G list , and make
* adjustments accordingly . This will result in a page list that looks
* like this :
*
* - - - - < - - first page starts before the buffer
* | |
* | ////|-> ----
* | ////| | |
* - - - - | |
* | |
* - - - - | |
* | ////| | |
* | ////| | |
* | ////| | |
* - - - - | |
* | |
* - - - - | |
* | ////| | |
* | ////| | |
* | ////| | |
* - - - - | |
* | |
* - - - - | |
* | ////| | |
* | ////|-> ----
* | | < - - last page ends after the buffer
* - - - -
*
* The distance between the start of the first page and the start of the
* buffer is lb_offset . The hashed ( ///) areas are the parts of the
* page list that contain the actual buffer .
*
* The advantage of this approach is that the number of pages is
* equal to the number of entries in the S / G list that we give to the
* hypervisor .
*/
lb_offset = param . local_vaddr & ( PAGE_SIZE - 1 ) ;
2019-05-15 01:47:03 +03:00
if ( param . count = = 0 | |
param . count > U64_MAX - lb_offset - PAGE_SIZE + 1 )
return - EINVAL ;
2011-06-10 00:52:06 +04:00
num_pages = ( param . count + lb_offset + PAGE_SIZE - 1 ) > > PAGE_SHIFT ;
/* Allocate the buffers we need */
/*
* ' pages ' is an array of struct page pointers that ' s initialized by
2020-09-02 00:21:11 +03:00
* get_user_pages_fast ( ) .
2011-06-10 00:52:06 +04:00
*/
treewide: kzalloc() -> kcalloc()
The kzalloc() function has a 2-factor argument form, kcalloc(). This
patch replaces cases of:
kzalloc(a * b, gfp)
with:
kcalloc(a * b, gfp)
as well as handling cases of:
kzalloc(a * b * c, gfp)
with:
kzalloc(array3_size(a, b, c), gfp)
as it's slightly less ugly than:
kzalloc_array(array_size(a, b), c, gfp)
This does, however, attempt to ignore constant size factors like:
kzalloc(4 * 1024, gfp)
though any constants defined via macros get caught up in the conversion.
Any factors with a sizeof() of "unsigned char", "char", and "u8" were
dropped, since they're redundant.
The Coccinelle script used for this was:
// Fix redundant parens around sizeof().
@@
type TYPE;
expression THING, E;
@@
(
kzalloc(
- (sizeof(TYPE)) * E
+ sizeof(TYPE) * E
, ...)
|
kzalloc(
- (sizeof(THING)) * E
+ sizeof(THING) * E
, ...)
)
// Drop single-byte sizes and redundant parens.
@@
expression COUNT;
typedef u8;
typedef __u8;
@@
(
kzalloc(
- sizeof(u8) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(__u8) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(char) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(unsigned char) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(u8) * COUNT
+ COUNT
, ...)
|
kzalloc(
- sizeof(__u8) * COUNT
+ COUNT
, ...)
|
kzalloc(
- sizeof(char) * COUNT
+ COUNT
, ...)
|
kzalloc(
- sizeof(unsigned char) * COUNT
+ COUNT
, ...)
)
// 2-factor product with sizeof(type/expression) and identifier or constant.
@@
type TYPE;
expression THING;
identifier COUNT_ID;
constant COUNT_CONST;
@@
(
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * (COUNT_ID)
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * COUNT_ID
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * (COUNT_CONST)
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * COUNT_CONST
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * (COUNT_ID)
+ COUNT_ID, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * COUNT_ID
+ COUNT_ID, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * (COUNT_CONST)
+ COUNT_CONST, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * COUNT_CONST
+ COUNT_CONST, sizeof(THING)
, ...)
)
// 2-factor product, only identifiers.
@@
identifier SIZE, COUNT;
@@
- kzalloc
+ kcalloc
(
- SIZE * COUNT
+ COUNT, SIZE
, ...)
// 3-factor product with 1 sizeof(type) or sizeof(expression), with
// redundant parens removed.
@@
expression THING;
identifier STRIDE, COUNT;
type TYPE;
@@
(
kzalloc(
- sizeof(TYPE) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(TYPE) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(TYPE) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(TYPE) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(THING) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kzalloc(
- sizeof(THING) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kzalloc(
- sizeof(THING) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kzalloc(
- sizeof(THING) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
)
// 3-factor product with 2 sizeof(variable), with redundant parens removed.
@@
expression THING1, THING2;
identifier COUNT;
type TYPE1, TYPE2;
@@
(
kzalloc(
- sizeof(TYPE1) * sizeof(TYPE2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
kzalloc(
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
kzalloc(
- sizeof(THING1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
kzalloc(
- sizeof(THING1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
kzalloc(
- sizeof(TYPE1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
|
kzalloc(
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
)
// 3-factor product, only identifiers, with redundant parens removed.
@@
identifier STRIDE, SIZE, COUNT;
@@
(
kzalloc(
- (COUNT) * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- (COUNT) * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- (COUNT) * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- (COUNT) * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
)
// Any remaining multi-factor products, first at least 3-factor products,
// when they're not all constants...
@@
expression E1, E2, E3;
constant C1, C2, C3;
@@
(
kzalloc(C1 * C2 * C3, ...)
|
kzalloc(
- (E1) * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
|
kzalloc(
- (E1) * (E2) * E3
+ array3_size(E1, E2, E3)
, ...)
|
kzalloc(
- (E1) * (E2) * (E3)
+ array3_size(E1, E2, E3)
, ...)
|
kzalloc(
- E1 * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
)
// And then all remaining 2 factors products when they're not all constants,
// keeping sizeof() as the second factor argument.
@@
expression THING, E1, E2;
type TYPE;
constant C1, C2, C3;
@@
(
kzalloc(sizeof(THING) * C2, ...)
|
kzalloc(sizeof(TYPE) * C2, ...)
|
kzalloc(C1 * C2 * C3, ...)
|
kzalloc(C1 * C2, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * (E2)
+ E2, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * E2
+ E2, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * (E2)
+ E2, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * E2
+ E2, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- (E1) * E2
+ E1, E2
, ...)
|
- kzalloc
+ kcalloc
(
- (E1) * (E2)
+ E1, E2
, ...)
|
- kzalloc
+ kcalloc
(
- E1 * E2
+ E1, E2
, ...)
)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-06-13 00:03:40 +03:00
pages = kcalloc ( num_pages , sizeof ( struct page * ) , GFP_KERNEL ) ;
2011-06-10 00:52:06 +04:00
if ( ! pages ) {
pr_debug ( " fsl-hv: could not allocate page list \n " ) ;
return - ENOMEM ;
}
/*
* sg_list is the list of fh_sg_list objects that we pass to the
* hypervisor .
*/
sg_list_unaligned = kmalloc ( num_pages * sizeof ( struct fh_sg_list ) +
sizeof ( struct fh_sg_list ) - 1 , GFP_KERNEL ) ;
if ( ! sg_list_unaligned ) {
pr_debug ( " fsl-hv: could not allocate S/G list \n " ) ;
ret = - ENOMEM ;
2020-09-02 00:21:11 +03:00
goto free_pages ;
2011-06-10 00:52:06 +04:00
}
sg_list = PTR_ALIGN ( sg_list_unaligned , sizeof ( struct fh_sg_list ) ) ;
/* Get the physical addresses of the source buffer */
2017-01-30 10:45:08 +03:00
num_pinned = get_user_pages_fast ( param . local_vaddr - lb_offset ,
2019-05-14 03:17:11 +03:00
num_pages , param . source ! = - 1 ? FOLL_WRITE : 0 , pages ) ;
2011-06-10 00:52:06 +04:00
if ( num_pinned ! = num_pages ) {
pr_debug ( " fsl-hv: could not lock source buffer \n " ) ;
ret = ( num_pinned < 0 ) ? num_pinned : - EFAULT ;
goto exit ;
}
/*
* Build the fh_sg_list [ ] array . The first page is special
* because it ' s misaligned .
*/
if ( param . source = = - 1 ) {
sg_list [ 0 ] . source = page_to_phys ( pages [ 0 ] ) + lb_offset ;
sg_list [ 0 ] . target = param . remote_paddr ;
} else {
sg_list [ 0 ] . source = param . remote_paddr ;
sg_list [ 0 ] . target = page_to_phys ( pages [ 0 ] ) + lb_offset ;
}
sg_list [ 0 ] . size = min_t ( uint64_t , param . count , PAGE_SIZE - lb_offset ) ;
remote_paddr = param . remote_paddr + sg_list [ 0 ] . size ;
count = param . count - sg_list [ 0 ] . size ;
for ( i = 1 ; i < num_pages ; i + + ) {
if ( param . source = = - 1 ) {
/* local to remote */
sg_list [ i ] . source = page_to_phys ( pages [ i ] ) ;
sg_list [ i ] . target = remote_paddr ;
} else {
/* remote to local */
sg_list [ i ] . source = remote_paddr ;
sg_list [ i ] . target = page_to_phys ( pages [ i ] ) ;
}
sg_list [ i ] . size = min_t ( uint64_t , count , PAGE_SIZE ) ;
remote_paddr + = sg_list [ i ] . size ;
count - = sg_list [ i ] . size ;
}
param . ret = fh_partition_memcpy ( param . source , param . target ,
virt_to_phys ( sg_list ) , num_pages ) ;
exit :
2020-09-02 00:21:11 +03:00
if ( pages & & ( num_pinned > 0 ) ) {
for ( i = 0 ; i < num_pinned ; i + + )
put_page ( pages [ i ] ) ;
2011-06-10 00:52:06 +04:00
}
kfree ( sg_list_unaligned ) ;
2020-09-02 00:21:11 +03:00
free_pages :
2011-06-10 00:52:06 +04:00
kfree ( pages ) ;
if ( ! ret )
if ( copy_to_user ( & p - > ret , & param . ret , sizeof ( __u32 ) ) )
return - EFAULT ;
return ret ;
}
/*
* Ioctl interface for FSL_HV_IOCTL_DOORBELL
*
* Ring a doorbell
*/
static long ioctl_doorbell ( struct fsl_hv_ioctl_doorbell __user * p )
{
struct fsl_hv_ioctl_doorbell param ;
/* Get the parameters from the user. */
if ( copy_from_user ( & param , p , sizeof ( struct fsl_hv_ioctl_doorbell ) ) )
return - EFAULT ;
param . ret = ev_doorbell_send ( param . doorbell ) ;
if ( copy_to_user ( & p - > ret , & param . ret , sizeof ( __u32 ) ) )
return - EFAULT ;
return 0 ;
}
static long ioctl_dtprop ( struct fsl_hv_ioctl_prop __user * p , int set )
{
struct fsl_hv_ioctl_prop param ;
char __user * upath , * upropname ;
void __user * upropval ;
2019-05-15 01:47:00 +03:00
char * path , * propname ;
void * propval ;
2011-06-10 00:52:06 +04:00
int ret = 0 ;
/* Get the parameters from the user. */
if ( copy_from_user ( & param , p , sizeof ( struct fsl_hv_ioctl_prop ) ) )
return - EFAULT ;
upath = ( char __user * ) ( uintptr_t ) param . path ;
upropname = ( char __user * ) ( uintptr_t ) param . propname ;
upropval = ( void __user * ) ( uintptr_t ) param . propval ;
path = strndup_user ( upath , FH_DTPROP_MAX_PATHLEN ) ;
2019-05-15 01:47:00 +03:00
if ( IS_ERR ( path ) )
return PTR_ERR ( path ) ;
2011-06-10 00:52:06 +04:00
propname = strndup_user ( upropname , FH_DTPROP_MAX_PATHLEN ) ;
if ( IS_ERR ( propname ) ) {
ret = PTR_ERR ( propname ) ;
2019-05-15 01:47:00 +03:00
goto err_free_path ;
2011-06-10 00:52:06 +04:00
}
if ( param . proplen > FH_DTPROP_MAX_PROPLEN ) {
ret = - EINVAL ;
2019-05-15 01:47:00 +03:00
goto err_free_propname ;
2011-06-10 00:52:06 +04:00
}
propval = kmalloc ( param . proplen , GFP_KERNEL ) ;
if ( ! propval ) {
ret = - ENOMEM ;
2019-05-15 01:47:00 +03:00
goto err_free_propname ;
2011-06-10 00:52:06 +04:00
}
if ( set ) {
if ( copy_from_user ( propval , upropval , param . proplen ) ) {
ret = - EFAULT ;
2019-05-15 01:47:00 +03:00
goto err_free_propval ;
2011-06-10 00:52:06 +04:00
}
param . ret = fh_partition_set_dtprop ( param . handle ,
virt_to_phys ( path ) ,
virt_to_phys ( propname ) ,
virt_to_phys ( propval ) ,
param . proplen ) ;
} else {
param . ret = fh_partition_get_dtprop ( param . handle ,
virt_to_phys ( path ) ,
virt_to_phys ( propname ) ,
virt_to_phys ( propval ) ,
& param . proplen ) ;
if ( param . ret = = 0 ) {
if ( copy_to_user ( upropval , propval , param . proplen ) | |
put_user ( param . proplen , & p - > proplen ) ) {
ret = - EFAULT ;
2019-05-15 01:47:00 +03:00
goto err_free_propval ;
2011-06-10 00:52:06 +04:00
}
}
}
if ( put_user ( param . ret , & p - > ret ) )
ret = - EFAULT ;
2019-05-15 01:47:00 +03:00
err_free_propval :
2011-06-10 00:52:06 +04:00
kfree ( propval ) ;
2019-05-15 01:47:00 +03:00
err_free_propname :
2011-06-10 00:52:06 +04:00
kfree ( propname ) ;
2019-05-15 01:47:00 +03:00
err_free_path :
kfree ( path ) ;
2011-06-10 00:52:06 +04:00
return ret ;
}
/*
* Ioctl main entry point
*/
static long fsl_hv_ioctl ( struct file * file , unsigned int cmd ,
unsigned long argaddr )
{
void __user * arg = ( void __user * ) argaddr ;
long ret ;
switch ( cmd ) {
case FSL_HV_IOCTL_PARTITION_RESTART :
ret = ioctl_restart ( arg ) ;
break ;
case FSL_HV_IOCTL_PARTITION_GET_STATUS :
ret = ioctl_status ( arg ) ;
break ;
case FSL_HV_IOCTL_PARTITION_START :
ret = ioctl_start ( arg ) ;
break ;
case FSL_HV_IOCTL_PARTITION_STOP :
ret = ioctl_stop ( arg ) ;
break ;
case FSL_HV_IOCTL_MEMCPY :
ret = ioctl_memcpy ( arg ) ;
break ;
case FSL_HV_IOCTL_DOORBELL :
ret = ioctl_doorbell ( arg ) ;
break ;
case FSL_HV_IOCTL_GETPROP :
ret = ioctl_dtprop ( arg , 0 ) ;
break ;
case FSL_HV_IOCTL_SETPROP :
ret = ioctl_dtprop ( arg , 1 ) ;
break ;
default :
pr_debug ( " fsl-hv: bad ioctl dir=%u type=%u cmd=%u size=%u \n " ,
_IOC_DIR ( cmd ) , _IOC_TYPE ( cmd ) , _IOC_NR ( cmd ) ,
_IOC_SIZE ( cmd ) ) ;
return - ENOTTY ;
}
return ret ;
}
/* Linked list of processes that have us open */
static struct list_head db_list ;
/* spinlock for db_list */
static DEFINE_SPINLOCK ( db_list_lock ) ;
/* The size of the doorbell event queue. This must be a power of two. */
# define QSIZE 16
/* Returns the next head/tail pointer, wrapping around the queue if necessary */
# define nextp(x) (((x) + 1) & (QSIZE - 1))
/* Per-open data structure */
struct doorbell_queue {
struct list_head list ;
spinlock_t lock ;
wait_queue_head_t wait ;
unsigned int head ;
unsigned int tail ;
uint32_t q [ QSIZE ] ;
} ;
/* Linked list of ISRs that we registered */
struct list_head isr_list ;
/* Per-ISR data structure */
struct doorbell_isr {
struct list_head list ;
unsigned int irq ;
uint32_t doorbell ; /* The doorbell handle */
uint32_t partition ; /* The partition handle, if used */
} ;
/*
* Add a doorbell to all of the doorbell queues
*/
static void fsl_hv_queue_doorbell ( uint32_t doorbell )
{
struct doorbell_queue * dbq ;
unsigned long flags ;
/* Prevent another core from modifying db_list */
spin_lock_irqsave ( & db_list_lock , flags ) ;
list_for_each_entry ( dbq , & db_list , list ) {
if ( dbq - > head ! = nextp ( dbq - > tail ) ) {
dbq - > q [ dbq - > tail ] = doorbell ;
/*
* This memory barrier eliminates the need to grab
* the spinlock for dbq .
*/
smp_wmb ( ) ;
dbq - > tail = nextp ( dbq - > tail ) ;
wake_up_interruptible ( & dbq - > wait ) ;
}
}
spin_unlock_irqrestore ( & db_list_lock , flags ) ;
}
/*
* Interrupt handler for all doorbells
*
* We use the same interrupt handler for all doorbells . Whenever a doorbell
* is rung , and we receive an interrupt , we just put the handle for that
* doorbell ( passed to us as * data ) into all of the queues .
*/
static irqreturn_t fsl_hv_isr ( int irq , void * data )
{
fsl_hv_queue_doorbell ( ( uintptr_t ) data ) ;
return IRQ_HANDLED ;
}
/*
* State change thread function
*
* The state change notification arrives in an interrupt , but we can ' t call
* blocking_notifier_call_chain ( ) in an interrupt handler . We could call
* atomic_notifier_call_chain ( ) , but that would require the clients ' call - back
* function to run in interrupt context . Since we don ' t want to impose that
* restriction on the clients , we use a threaded IRQ to process the
* notification in kernel context .
*/
static irqreturn_t fsl_hv_state_change_thread ( int irq , void * data )
{
struct doorbell_isr * dbisr = data ;
blocking_notifier_call_chain ( & failover_subscribers , dbisr - > partition ,
NULL ) ;
return IRQ_HANDLED ;
}
/*
* Interrupt handler for state - change doorbells
*/
static irqreturn_t fsl_hv_state_change_isr ( int irq , void * data )
{
unsigned int status ;
struct doorbell_isr * dbisr = data ;
int ret ;
/* It's still a doorbell, so add it to all the queues. */
fsl_hv_queue_doorbell ( dbisr - > doorbell ) ;
/* Determine the new state, and if it's stopped, notify the clients. */
ret = fh_partition_get_status ( dbisr - > partition , & status ) ;
if ( ! ret & & ( status = = FH_PARTITION_STOPPED ) )
return IRQ_WAKE_THREAD ;
return IRQ_HANDLED ;
}
/*
* Returns a bitmask indicating whether a read will block
*/
2017-07-03 13:39:46 +03:00
static __poll_t fsl_hv_poll ( struct file * filp , struct poll_table_struct * p )
2011-06-10 00:52:06 +04:00
{
struct doorbell_queue * dbq = filp - > private_data ;
unsigned long flags ;
2017-07-03 13:39:46 +03:00
__poll_t mask ;
2011-06-10 00:52:06 +04:00
spin_lock_irqsave ( & dbq - > lock , flags ) ;
poll_wait ( filp , & dbq - > wait , p ) ;
2018-02-12 01:34:03 +03:00
mask = ( dbq - > head = = dbq - > tail ) ? 0 : ( EPOLLIN | EPOLLRDNORM ) ;
2011-06-10 00:52:06 +04:00
spin_unlock_irqrestore ( & dbq - > lock , flags ) ;
return mask ;
}
/*
* Return the handles for any incoming doorbells
*
* If there are doorbell handles in the queue for this open instance , then
* return them to the caller as an array of 32 - bit integers . Otherwise ,
* block until there is at least one handle to return .
*/
static ssize_t fsl_hv_read ( struct file * filp , char __user * buf , size_t len ,
loff_t * off )
{
struct doorbell_queue * dbq = filp - > private_data ;
uint32_t __user * p = ( uint32_t __user * ) buf ; /* for put_user() */
unsigned long flags ;
ssize_t count = 0 ;
/* Make sure we stop when the user buffer is full. */
while ( len > = sizeof ( uint32_t ) ) {
uint32_t dbell ; /* Local copy of doorbell queue data */
spin_lock_irqsave ( & dbq - > lock , flags ) ;
/*
* If the queue is empty , then either we ' re done or we need
* to block . If the application specified O_NONBLOCK , then
* we return the appropriate error code .
*/
if ( dbq - > head = = dbq - > tail ) {
spin_unlock_irqrestore ( & dbq - > lock , flags ) ;
if ( count )
break ;
if ( filp - > f_flags & O_NONBLOCK )
return - EAGAIN ;
if ( wait_event_interruptible ( dbq - > wait ,
dbq - > head ! = dbq - > tail ) )
return - ERESTARTSYS ;
continue ;
}
/*
* Even though we have an smp_wmb ( ) in the ISR , the core
* might speculatively execute the " dbell = ... " below while
* it ' s evaluating the if - statement above . In that case , the
* value put into dbell could be stale if the core accepts the
* speculation . To prevent that , we need a read memory barrier
* here as well .
*/
smp_rmb ( ) ;
/* Copy the data to a temporary local buffer, because
* we can ' t call copy_to_user ( ) from inside a spinlock
*/
dbell = dbq - > q [ dbq - > head ] ;
dbq - > head = nextp ( dbq - > head ) ;
spin_unlock_irqrestore ( & dbq - > lock , flags ) ;
if ( put_user ( dbell , p ) )
return - EFAULT ;
p + + ;
count + = sizeof ( uint32_t ) ;
len - = sizeof ( uint32_t ) ;
}
return count ;
}
/*
* Open the driver and prepare for reading doorbells .
*
* Every time an application opens the driver , we create a doorbell queue
* for that file handle . This queue is used for any incoming doorbells .
*/
static int fsl_hv_open ( struct inode * inode , struct file * filp )
{
struct doorbell_queue * dbq ;
unsigned long flags ;
dbq = kzalloc ( sizeof ( struct doorbell_queue ) , GFP_KERNEL ) ;
if ( ! dbq ) {
pr_err ( " fsl-hv: out of memory \n " ) ;
return - ENOMEM ;
}
spin_lock_init ( & dbq - > lock ) ;
init_waitqueue_head ( & dbq - > wait ) ;
spin_lock_irqsave ( & db_list_lock , flags ) ;
list_add ( & dbq - > list , & db_list ) ;
spin_unlock_irqrestore ( & db_list_lock , flags ) ;
filp - > private_data = dbq ;
2022-04-26 11:33:13 +03:00
return 0 ;
2011-06-10 00:52:06 +04:00
}
/*
* Close the driver
*/
static int fsl_hv_close ( struct inode * inode , struct file * filp )
{
struct doorbell_queue * dbq = filp - > private_data ;
unsigned long flags ;
spin_lock_irqsave ( & db_list_lock , flags ) ;
list_del ( & dbq - > list ) ;
spin_unlock_irqrestore ( & db_list_lock , flags ) ;
kfree ( dbq ) ;
2022-03-11 05:38:51 +03:00
return 0 ;
2011-06-10 00:52:06 +04:00
}
static const struct file_operations fsl_hv_fops = {
. owner = THIS_MODULE ,
. open = fsl_hv_open ,
. release = fsl_hv_close ,
. poll = fsl_hv_poll ,
. read = fsl_hv_read ,
. unlocked_ioctl = fsl_hv_ioctl ,
2018-09-11 22:59:08 +03:00
. compat_ioctl = compat_ptr_ioctl ,
2011-06-10 00:52:06 +04:00
} ;
static struct miscdevice fsl_hv_misc_dev = {
MISC_DYNAMIC_MINOR ,
" fsl-hv " ,
& fsl_hv_fops
} ;
static irqreturn_t fsl_hv_shutdown_isr ( int irq , void * data )
{
orderly_poweroff ( false ) ;
return IRQ_HANDLED ;
}
/*
* Returns the handle of the parent of the given node
*
* The handle is the value of the ' hv - handle ' property
*/
static int get_parent_handle ( struct device_node * np )
{
struct device_node * parent ;
const uint32_t * prop ;
uint32_t handle ;
int len ;
parent = of_get_parent ( np ) ;
if ( ! parent )
/* It's not really possible for this to fail */
return - ENODEV ;
/*
* The proper name for the handle property is " hv-handle " , but some
* older versions of the hypervisor used " reg " .
*/
prop = of_get_property ( parent , " hv-handle " , & len ) ;
if ( ! prop )
prop = of_get_property ( parent , " reg " , & len ) ;
if ( ! prop | | ( len ! = sizeof ( uint32_t ) ) ) {
/* This can happen only if the node is malformed */
of_node_put ( parent ) ;
return - ENODEV ;
}
handle = be32_to_cpup ( prop ) ;
of_node_put ( parent ) ;
return handle ;
}
/*
* Register a callback for failover events
*
* This function is called by device drivers to register their callback
* functions for fail - over events .
*/
int fsl_hv_failover_register ( struct notifier_block * nb )
{
return blocking_notifier_chain_register ( & failover_subscribers , nb ) ;
}
EXPORT_SYMBOL ( fsl_hv_failover_register ) ;
/*
* Unregister a callback for failover events
*/
int fsl_hv_failover_unregister ( struct notifier_block * nb )
{
return blocking_notifier_chain_unregister ( & failover_subscribers , nb ) ;
}
EXPORT_SYMBOL ( fsl_hv_failover_unregister ) ;
/*
* Return TRUE if we ' re running under FSL hypervisor
*
* This function checks to see if we ' re running under the Freescale
* hypervisor , and returns zero if we ' re not , or non - zero if we are .
*
* First , it checks if MSR [ GS ] = = 1 , which means we ' re running under some
* hypervisor . Then it checks if there is a hypervisor node in the device
* tree . Currently , that means there needs to be a node in the root called
* " hypervisor " and which has a property named " fsl,hv-version " .
*/
static int has_fsl_hypervisor ( void )
{
struct device_node * node ;
int ret ;
node = of_find_node_by_path ( " /hypervisor " ) ;
if ( ! node )
return 0 ;
ret = of_find_property ( node , " fsl,hv-version " , NULL ) ! = NULL ;
of_node_put ( node ) ;
return ret ;
}
/*
* Freescale hypervisor management driver init
*
* This function is called when this module is loaded .
*
* Register ourselves as a miscellaneous driver . This will register the
* fops structure and create the right sysfs entries for udev .
*/
static int __init fsl_hypervisor_init ( void )
{
struct device_node * np ;
struct doorbell_isr * dbisr , * n ;
int ret ;
pr_info ( " Freescale hypervisor management driver \n " ) ;
if ( ! has_fsl_hypervisor ( ) ) {
pr_info ( " fsl-hv: no hypervisor found \n " ) ;
return - ENODEV ;
}
ret = misc_register ( & fsl_hv_misc_dev ) ;
if ( ret ) {
pr_err ( " fsl-hv: cannot register device \n " ) ;
return ret ;
}
INIT_LIST_HEAD ( & db_list ) ;
INIT_LIST_HEAD ( & isr_list ) ;
for_each_compatible_node ( np , NULL , " epapr,hv-receive-doorbell " ) {
unsigned int irq ;
const uint32_t * handle ;
handle = of_get_property ( np , " interrupts " , NULL ) ;
irq = irq_of_parse_and_map ( np , 0 ) ;
if ( ! handle | | ( irq = = NO_IRQ ) ) {
2017-06-07 00:45:15 +03:00
pr_err ( " fsl-hv: no 'interrupts' property in %pOF node \n " ,
np ) ;
2011-06-10 00:52:06 +04:00
continue ;
}
dbisr = kzalloc ( sizeof ( * dbisr ) , GFP_KERNEL ) ;
if ( ! dbisr )
goto out_of_memory ;
dbisr - > irq = irq ;
dbisr - > doorbell = be32_to_cpup ( handle ) ;
if ( of_device_is_compatible ( np , " fsl,hv-shutdown-doorbell " ) ) {
/* The shutdown doorbell gets its own ISR */
ret = request_irq ( irq , fsl_hv_shutdown_isr , 0 ,
np - > name , NULL ) ;
} else if ( of_device_is_compatible ( np ,
" fsl,hv-state-change-doorbell " ) ) {
/*
* The state change doorbell triggers a notification if
* the state of the managed partition changes to
* " stopped " . We need a separate interrupt handler for
* that , and we also need to know the handle of the
* target partition , not just the handle of the
* doorbell .
*/
dbisr - > partition = ret = get_parent_handle ( np ) ;
if ( ret < 0 ) {
2017-06-07 00:45:15 +03:00
pr_err ( " fsl-hv: node %pOF has missing or "
" malformed parent \n " , np ) ;
2011-06-10 00:52:06 +04:00
kfree ( dbisr ) ;
continue ;
}
ret = request_threaded_irq ( irq , fsl_hv_state_change_isr ,
fsl_hv_state_change_thread ,
0 , np - > name , dbisr ) ;
} else
ret = request_irq ( irq , fsl_hv_isr , 0 , np - > name , dbisr ) ;
if ( ret < 0 ) {
2017-06-07 00:45:15 +03:00
pr_err ( " fsl-hv: could not request irq %u for node %pOF \n " ,
irq , np ) ;
2011-06-10 00:52:06 +04:00
kfree ( dbisr ) ;
continue ;
}
list_add ( & dbisr - > list , & isr_list ) ;
pr_info ( " fsl-hv: registered handler for doorbell %u \n " ,
dbisr - > doorbell ) ;
}
return 0 ;
out_of_memory :
list_for_each_entry_safe ( dbisr , n , & isr_list , list ) {
free_irq ( dbisr - > irq , dbisr ) ;
list_del ( & dbisr - > list ) ;
kfree ( dbisr ) ;
}
misc_deregister ( & fsl_hv_misc_dev ) ;
return - ENOMEM ;
}
/*
* Freescale hypervisor management driver termination
*
* This function is called when this driver is unloaded .
*/
static void __exit fsl_hypervisor_exit ( void )
{
struct doorbell_isr * dbisr , * n ;
list_for_each_entry_safe ( dbisr , n , & isr_list , list ) {
free_irq ( dbisr - > irq , dbisr ) ;
list_del ( & dbisr - > list ) ;
kfree ( dbisr ) ;
}
misc_deregister ( & fsl_hv_misc_dev ) ;
}
module_init ( fsl_hypervisor_init ) ;
module_exit ( fsl_hypervisor_exit ) ;
MODULE_AUTHOR ( " Timur Tabi <timur@freescale.com> " ) ;
MODULE_DESCRIPTION ( " Freescale hypervisor management driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;