2012-06-11 16:38:08 +04:00
/******************************************************************************
* pcpu . c
* Management physical cpu in dom0 , get pcpu info and provide sys interface
*
* Copyright ( c ) 2012 Intel Corporation
* Author : Liu , Jinsong < jinsong . liu @ intel . com >
* Author : Jiang , Yunhong < yunhong . jiang @ intel . com >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation ; or , when distributed
* separately from the Linux kernel or incorporated into other
* software packages , subject to the following license :
*
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this source file ( the " Software " ) , to deal in the Software without
* restriction , including without limitation the rights to use , copy , modify ,
* merge , publish , distribute , sublicense , and / or sell copies of the Software ,
* and to permit persons to whom the Software is furnished to do so , subject to
* the following conditions :
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING
* FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE .
*/
2013-06-28 14:21:41 +04:00
# define pr_fmt(fmt) "xen_cpu: " fmt
2012-06-11 16:38:08 +04:00
# include <linux/interrupt.h>
# include <linux/spinlock.h>
# include <linux/cpu.h>
# include <linux/stat.h>
# include <linux/capability.h>
# include <xen/xen.h>
# include <xen/xenbus.h>
# include <xen/events.h>
# include <xen/interface/platform.h>
# include <asm/xen/hypervisor.h>
# include <asm/xen/hypercall.h>
/*
* @ cpu_id : Xen physical cpu logic number
* @ flags : Xen physical cpu status flag
* - XEN_PCPU_FLAGS_ONLINE : cpu is online
* - XEN_PCPU_FLAGS_INVALID : cpu is not present
*/
struct pcpu {
struct list_head list ;
struct device dev ;
uint32_t cpu_id ;
uint32_t flags ;
} ;
static struct bus_type xen_pcpu_subsys = {
. name = " xen_cpu " ,
. dev_name = " xen_cpu " ,
} ;
static DEFINE_MUTEX ( xen_pcpu_lock ) ;
static LIST_HEAD ( xen_pcpus ) ;
static int xen_pcpu_down ( uint32_t cpu_id )
{
struct xen_platform_op op = {
. cmd = XENPF_cpu_offline ,
. interface_version = XENPF_INTERFACE_VERSION ,
. u . cpu_ol . cpuid = cpu_id ,
} ;
return HYPERVISOR_dom0_op ( & op ) ;
}
static int xen_pcpu_up ( uint32_t cpu_id )
{
struct xen_platform_op op = {
. cmd = XENPF_cpu_online ,
. interface_version = XENPF_INTERFACE_VERSION ,
. u . cpu_ol . cpuid = cpu_id ,
} ;
return HYPERVISOR_dom0_op ( & op ) ;
}
static ssize_t show_online ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
struct pcpu * cpu = container_of ( dev , struct pcpu , dev ) ;
return sprintf ( buf , " %u \n " , ! ! ( cpu - > flags & XEN_PCPU_FLAGS_ONLINE ) ) ;
}
static ssize_t __ref store_online ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
struct pcpu * pcpu = container_of ( dev , struct pcpu , dev ) ;
unsigned long long val ;
ssize_t ret ;
if ( ! capable ( CAP_SYS_ADMIN ) )
return - EPERM ;
if ( kstrtoull ( buf , 0 , & val ) < 0 )
return - EINVAL ;
switch ( val ) {
case 0 :
ret = xen_pcpu_down ( pcpu - > cpu_id ) ;
break ;
case 1 :
ret = xen_pcpu_up ( pcpu - > cpu_id ) ;
break ;
default :
ret = - EINVAL ;
}
if ( ret > = 0 )
ret = count ;
return ret ;
}
static DEVICE_ATTR ( online , S_IRUGO | S_IWUSR , show_online , store_online ) ;
static bool xen_pcpu_online ( uint32_t flags )
{
return ! ! ( flags & XEN_PCPU_FLAGS_ONLINE ) ;
}
static void pcpu_online_status ( struct xenpf_pcpuinfo * info ,
struct pcpu * pcpu )
{
if ( xen_pcpu_online ( info - > flags ) & &
! xen_pcpu_online ( pcpu - > flags ) ) {
/* the pcpu is onlined */
pcpu - > flags | = XEN_PCPU_FLAGS_ONLINE ;
kobject_uevent ( & pcpu - > dev . kobj , KOBJ_ONLINE ) ;
} else if ( ! xen_pcpu_online ( info - > flags ) & &
xen_pcpu_online ( pcpu - > flags ) ) {
/* The pcpu is offlined */
pcpu - > flags & = ~ XEN_PCPU_FLAGS_ONLINE ;
kobject_uevent ( & pcpu - > dev . kobj , KOBJ_OFFLINE ) ;
}
}
static struct pcpu * get_pcpu ( uint32_t cpu_id )
{
struct pcpu * pcpu ;
list_for_each_entry ( pcpu , & xen_pcpus , list ) {
if ( pcpu - > cpu_id = = cpu_id )
return pcpu ;
}
return NULL ;
}
static void pcpu_release ( struct device * dev )
{
struct pcpu * pcpu = container_of ( dev , struct pcpu , dev ) ;
list_del ( & pcpu - > list ) ;
kfree ( pcpu ) ;
}
static void unregister_and_remove_pcpu ( struct pcpu * pcpu )
{
struct device * dev ;
if ( ! pcpu )
return ;
dev = & pcpu - > dev ;
if ( dev - > id )
device_remove_file ( dev , & dev_attr_online ) ;
/* pcpu remove would be implicitly done */
device_unregister ( dev ) ;
}
static int register_pcpu ( struct pcpu * pcpu )
{
struct device * dev ;
int err = - EINVAL ;
if ( ! pcpu )
return err ;
dev = & pcpu - > dev ;
dev - > bus = & xen_pcpu_subsys ;
dev - > id = pcpu - > cpu_id ;
dev - > release = pcpu_release ;
err = device_register ( dev ) ;
if ( err ) {
pcpu_release ( dev ) ;
return err ;
}
/*
* Xen never offline cpu0 due to several restrictions
* and assumptions . This basically doesn ' t add a sys control
* to user , one cannot attempt to offline BSP .
*/
if ( dev - > id ) {
err = device_create_file ( dev , & dev_attr_online ) ;
if ( err ) {
device_unregister ( dev ) ;
return err ;
}
}
return 0 ;
}
static struct pcpu * create_and_register_pcpu ( struct xenpf_pcpuinfo * info )
{
struct pcpu * pcpu ;
int err ;
if ( info - > flags & XEN_PCPU_FLAGS_INVALID )
return ERR_PTR ( - ENODEV ) ;
pcpu = kzalloc ( sizeof ( struct pcpu ) , GFP_KERNEL ) ;
if ( ! pcpu )
return ERR_PTR ( - ENOMEM ) ;
INIT_LIST_HEAD ( & pcpu - > list ) ;
pcpu - > cpu_id = info - > xen_cpuid ;
pcpu - > flags = info - > flags ;
/* Need hold on xen_pcpu_lock before pcpu list manipulations */
list_add_tail ( & pcpu - > list , & xen_pcpus ) ;
err = register_pcpu ( pcpu ) ;
if ( err ) {
2013-06-28 14:21:41 +04:00
pr_warn ( " Failed to register pcpu%u \n " , info - > xen_cpuid ) ;
2012-06-11 16:38:08 +04:00
return ERR_PTR ( - ENOENT ) ;
}
return pcpu ;
}
/*
* Caller should hold the xen_pcpu_lock
*/
static int sync_pcpu ( uint32_t cpu , uint32_t * max_cpu )
{
int ret ;
struct pcpu * pcpu = NULL ;
struct xenpf_pcpuinfo * info ;
struct xen_platform_op op = {
. cmd = XENPF_get_cpuinfo ,
. interface_version = XENPF_INTERFACE_VERSION ,
. u . pcpu_info . xen_cpuid = cpu ,
} ;
ret = HYPERVISOR_dom0_op ( & op ) ;
if ( ret )
return ret ;
info = & op . u . pcpu_info ;
if ( max_cpu )
* max_cpu = info - > max_present ;
pcpu = get_pcpu ( cpu ) ;
/*
* Only those at cpu present map has its sys interface .
*/
if ( info - > flags & XEN_PCPU_FLAGS_INVALID ) {
2013-02-12 08:01:53 +04:00
unregister_and_remove_pcpu ( pcpu ) ;
2012-06-11 16:38:08 +04:00
return 0 ;
}
if ( ! pcpu ) {
pcpu = create_and_register_pcpu ( info ) ;
if ( IS_ERR_OR_NULL ( pcpu ) )
return - ENODEV ;
} else
pcpu_online_status ( info , pcpu ) ;
return 0 ;
}
/*
* Sync dom0 ' s pcpu information with xen hypervisor ' s
*/
static int xen_sync_pcpus ( void )
{
/*
* Boot cpu always have cpu_id 0 in xen
*/
uint32_t cpu = 0 , max_cpu = 0 ;
int err = 0 ;
struct pcpu * pcpu , * tmp ;
mutex_lock ( & xen_pcpu_lock ) ;
while ( ! err & & ( cpu < = max_cpu ) ) {
err = sync_pcpu ( cpu , & max_cpu ) ;
cpu + + ;
}
if ( err )
list_for_each_entry_safe ( pcpu , tmp , & xen_pcpus , list )
unregister_and_remove_pcpu ( pcpu ) ;
mutex_unlock ( & xen_pcpu_lock ) ;
return err ;
}
static void xen_pcpu_work_fn ( struct work_struct * work )
{
xen_sync_pcpus ( ) ;
}
static DECLARE_WORK ( xen_pcpu_work , xen_pcpu_work_fn ) ;
static irqreturn_t xen_pcpu_interrupt ( int irq , void * dev_id )
{
schedule_work ( & xen_pcpu_work ) ;
return IRQ_HANDLED ;
}
2013-01-25 11:43:34 +04:00
/* Sync with Xen hypervisor after cpu hotadded */
void xen_pcpu_hotplug_sync ( void )
{
schedule_work ( & xen_pcpu_work ) ;
}
EXPORT_SYMBOL_GPL ( xen_pcpu_hotplug_sync ) ;
/*
* For hypervisor presented cpu , return logic cpu id ;
* For hypervisor non - presented cpu , return - ENODEV .
*/
int xen_pcpu_id ( uint32_t acpi_id )
{
int cpu_id = 0 , max_id = 0 ;
struct xen_platform_op op ;
op . cmd = XENPF_get_cpuinfo ;
while ( cpu_id < = max_id ) {
op . u . pcpu_info . xen_cpuid = cpu_id ;
if ( HYPERVISOR_dom0_op ( & op ) ) {
cpu_id + + ;
continue ;
}
if ( acpi_id = = op . u . pcpu_info . acpi_id )
return cpu_id ;
if ( op . u . pcpu_info . max_present > max_id )
max_id = op . u . pcpu_info . max_present ;
cpu_id + + ;
}
return - ENODEV ;
}
EXPORT_SYMBOL_GPL ( xen_pcpu_id ) ;
2012-06-11 16:38:08 +04:00
static int __init xen_pcpu_init ( void )
{
int irq , ret ;
if ( ! xen_initial_domain ( ) )
return - ENODEV ;
irq = bind_virq_to_irqhandler ( VIRQ_PCPU_STATE , 0 ,
xen_pcpu_interrupt , 0 ,
" xen-pcpu " , NULL ) ;
if ( irq < 0 ) {
2013-06-28 14:21:41 +04:00
pr_warn ( " Failed to bind pcpu virq \n " ) ;
2012-06-11 16:38:08 +04:00
return irq ;
}
ret = subsys_system_register ( & xen_pcpu_subsys , NULL ) ;
if ( ret ) {
2013-06-28 14:21:41 +04:00
pr_warn ( " Failed to register pcpu subsys \n " ) ;
2012-06-11 16:38:08 +04:00
goto err1 ;
}
ret = xen_sync_pcpus ( ) ;
if ( ret ) {
2013-06-28 14:21:41 +04:00
pr_warn ( " Failed to sync pcpu info \n " ) ;
2012-06-11 16:38:08 +04:00
goto err2 ;
}
return 0 ;
err2 :
bus_unregister ( & xen_pcpu_subsys ) ;
err1 :
unbind_from_irqhandler ( irq , NULL ) ;
return ret ;
}
arch_initcall ( xen_pcpu_init ) ;