2005-04-17 02:20:36 +04:00
/*
* Interface for Dynamic Logical Partitioning of I / O Slots on
* RPA - compliant PPC64 platform .
*
* John Rose < johnrose @ austin . ibm . com >
* Linda Xie < lxie @ us . ibm . com >
*
* October 2003
*
* Copyright ( C ) 2003 IBM .
*
* 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 .
*/
# include <linux/init.h>
# include <linux/pci.h>
2005-10-31 02:03:48 +03:00
# include <linux/string.h>
2005-04-17 02:20:36 +04:00
# include <asm/pci-bridge.h>
# include <asm/semaphore.h>
# include <asm/rtas.h>
2005-07-25 19:16:42 +04:00
# include <asm/vio.h>
2005-10-31 02:03:48 +03:00
2005-04-17 02:20:36 +04:00
# include "../pci.h"
# include "rpaphp.h"
# include "rpadlpar.h"
static DECLARE_MUTEX ( rpadlpar_sem ) ;
2005-07-25 19:17:03 +04:00
# define DLPAR_MODULE_NAME "rpadlpar_io"
2005-04-17 02:20:36 +04:00
# define NODE_TYPE_VIO 1
# define NODE_TYPE_SLOT 2
# define NODE_TYPE_PHB 3
2005-07-25 19:16:42 +04:00
static struct device_node * find_vio_slot_node ( char * drc_name )
2005-04-17 02:20:36 +04:00
{
struct device_node * parent = of_find_node_by_name ( NULL , " vdevice " ) ;
2005-07-25 19:16:42 +04:00
struct device_node * dn = NULL ;
char * name ;
int rc ;
2005-04-17 02:20:36 +04:00
if ( ! parent )
return NULL ;
2005-07-25 19:16:42 +04:00
while ( ( dn = of_get_next_child ( parent , dn ) ) ) {
rc = rpaphp_get_drc_props ( dn , NULL , & name , NULL , NULL ) ;
if ( ( rc = = 0 ) & & ( ! strcmp ( drc_name , name ) ) )
break ;
2005-04-17 02:20:36 +04:00
}
2005-07-25 19:16:42 +04:00
return dn ;
2005-04-17 02:20:36 +04:00
}
/* Find dlpar-capable pci node that contains the specified name and type */
static struct device_node * find_php_slot_pci_node ( char * drc_name ,
char * drc_type )
{
struct device_node * np = NULL ;
char * name ;
char * type ;
int rc ;
while ( ( np = of_find_node_by_type ( np , " pci " ) ) ) {
rc = rpaphp_get_drc_props ( np , NULL , & name , & type , NULL ) ;
if ( rc = = 0 )
if ( ! strcmp ( drc_name , name ) & & ! strcmp ( drc_type , type ) )
break ;
}
return np ;
}
2005-07-25 19:16:42 +04:00
static struct device_node * find_dlpar_node ( char * drc_name , int * node_type )
2005-04-17 02:20:36 +04:00
{
struct device_node * dn ;
dn = find_php_slot_pci_node ( drc_name , " SLOT " ) ;
if ( dn ) {
* node_type = NODE_TYPE_SLOT ;
return dn ;
}
dn = find_php_slot_pci_node ( drc_name , " PHB " ) ;
if ( dn ) {
* node_type = NODE_TYPE_PHB ;
return dn ;
}
2005-07-25 19:16:42 +04:00
dn = find_vio_slot_node ( drc_name ) ;
2005-04-17 02:20:36 +04:00
if ( dn ) {
* node_type = NODE_TYPE_VIO ;
return dn ;
}
return NULL ;
}
2005-07-25 19:17:03 +04:00
static struct slot * find_slot ( struct device_node * dn )
2005-04-17 02:20:36 +04:00
{
struct list_head * tmp , * n ;
struct slot * slot ;
list_for_each_safe ( tmp , n , & rpaphp_slot_head ) {
slot = list_entry ( tmp , struct slot , rpaphp_slot_list ) ;
2005-07-25 19:17:03 +04:00
if ( slot - > dn = = dn )
2005-04-17 02:20:36 +04:00
return slot ;
}
return NULL ;
}
2005-07-25 19:16:53 +04:00
static struct pci_dev * dlpar_find_new_dev ( struct pci_bus * parent ,
struct device_node * dev_dn )
{
struct pci_dev * tmp = NULL ;
struct device_node * child_dn ;
list_for_each_entry ( tmp , & parent - > devices , bus_list ) {
child_dn = pci_device_to_OF_node ( tmp ) ;
if ( child_dn = = dev_dn )
return tmp ;
}
return NULL ;
}
2005-04-17 02:20:36 +04:00
static struct pci_dev * dlpar_pci_add_bus ( struct device_node * dn )
{
2005-09-06 07:17:54 +04:00
struct pci_dn * pdn = dn - > data ;
2005-11-05 00:38:50 +03:00
struct pci_controller * phb = pdn - > phb ;
2005-04-17 02:20:36 +04:00
struct pci_dev * dev = NULL ;
2005-12-02 03:56:14 +03:00
eeh_add_device_tree_early ( dn ) ;
2005-11-05 00:38:50 +03:00
/* Add EADS device to PHB bus, adding new entry to bus->devices */
dev = of_create_pci_dev ( dn , phb - > bus , pdn - > devfn ) ;
if ( ! dev ) {
printk ( KERN_ERR " %s: failed to create pci dev for %s \n " ,
__FUNCTION__ , dn - > full_name ) ;
2005-04-17 02:20:36 +04:00
return NULL ;
}
2005-11-05 00:38:50 +03:00
if ( dev - > hdr_type = = PCI_HEADER_TYPE_BRIDGE | |
dev - > hdr_type = = PCI_HEADER_TYPE_CARDBUS )
of_scan_pci_bridge ( dn , dev ) ;
rpaphp_init_new_devs ( dev - > subordinate ) ;
/* Claim new bus resources */
2005-12-02 03:59:58 +03:00
pcibios_claim_one_bus ( dev - > bus ) ;
2005-11-05 00:38:50 +03:00
/* ioremap() for child bus, which may or may not succeed */
2006-01-12 23:36:25 +03:00
remap_bus_range ( dev - > subordinate ) ;
2005-11-05 00:38:50 +03:00
2005-04-17 02:20:36 +04:00
/* Add new devices to global lists. Register in proc, sysfs. */
2005-11-05 00:38:50 +03:00
pci_bus_add_devices ( phb - > bus ) ;
2005-04-17 02:20:36 +04:00
/* Confirm new bridge dev was created */
2005-11-05 00:38:50 +03:00
dev = dlpar_find_new_dev ( phb - > bus , dn ) ;
2005-07-25 19:16:53 +04:00
if ( dev ) {
if ( dev - > hdr_type ! = PCI_HEADER_TYPE_BRIDGE ) {
printk ( KERN_ERR " %s: unexpected header type %d \n " ,
__FUNCTION__ , dev - > hdr_type ) ;
return NULL ;
}
2005-04-17 02:20:36 +04:00
}
return dev ;
}
2005-07-25 19:16:58 +04:00
static int dlpar_add_pci_slot ( char * drc_name , struct device_node * dn )
2005-04-17 02:20:36 +04:00
{
struct pci_dev * dev ;
2005-07-25 19:17:03 +04:00
if ( rpaphp_find_pci_bus ( dn ) )
return - EINVAL ;
2005-04-17 02:20:36 +04:00
/* Add pci bus */
dev = dlpar_pci_add_bus ( dn ) ;
if ( ! dev ) {
printk ( KERN_ERR " %s: unable to add bus %s \n " , __FUNCTION__ ,
drc_name ) ;
return - EIO ;
}
2005-07-25 19:16:42 +04:00
/* Add hotplug slot */
if ( rpaphp_add_slot ( dn ) ) {
printk ( KERN_ERR " %s: unable to add hotplug slot %s \n " ,
__FUNCTION__ , drc_name ) ;
return - EIO ;
}
2005-04-17 02:20:36 +04:00
return 0 ;
}
static int dlpar_remove_root_bus ( struct pci_controller * phb )
{
struct pci_bus * phb_bus ;
int rc ;
phb_bus = phb - > bus ;
if ( ! ( list_empty ( & phb_bus - > children ) & &
list_empty ( & phb_bus - > devices ) ) ) {
return - EBUSY ;
}
rc = pcibios_remove_root_bus ( phb ) ;
if ( rc )
return - EIO ;
device_unregister ( phb_bus - > bridge ) ;
pci_remove_bus ( phb_bus ) ;
return 0 ;
}
2005-07-25 19:17:03 +04:00
static int dlpar_remove_phb ( char * drc_name , struct device_node * dn )
2005-04-17 02:20:36 +04:00
{
2005-07-25 19:17:03 +04:00
struct slot * slot ;
2005-09-06 07:17:54 +04:00
struct pci_dn * pdn ;
2005-04-17 02:20:36 +04:00
int rc = 0 ;
2005-07-25 19:17:03 +04:00
if ( ! rpaphp_find_pci_bus ( dn ) )
return - EINVAL ;
2005-04-17 02:20:36 +04:00
2005-07-25 19:17:03 +04:00
slot = find_slot ( dn ) ;
if ( slot ) {
/* Remove hotplug slot */
if ( rpaphp_remove_slot ( slot ) ) {
printk ( KERN_ERR
" %s: unable to remove hotplug slot %s \n " ,
__FUNCTION__ , drc_name ) ;
return - EIO ;
}
2005-04-17 02:20:36 +04:00
}
2005-09-06 07:17:54 +04:00
pdn = dn - > data ;
BUG_ON ( ! pdn | | ! pdn - > phb ) ;
rc = dlpar_remove_root_bus ( pdn - > phb ) ;
2005-04-17 02:20:36 +04:00
if ( rc < 0 )
return rc ;
2005-09-06 07:17:54 +04:00
pdn - > phb = NULL ;
2005-07-25 19:17:03 +04:00
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-07-25 19:16:42 +04:00
static int dlpar_add_phb ( char * drc_name , struct device_node * dn )
2005-04-17 02:20:36 +04:00
{
struct pci_controller * phb ;
2005-11-04 03:51:17 +03:00
if ( PCI_DN ( dn ) & & PCI_DN ( dn ) - > phb ) {
2005-07-25 19:17:03 +04:00
/* PHB already exists */
return - EINVAL ;
}
2005-04-17 02:20:36 +04:00
phb = init_phb_dynamic ( dn ) ;
if ( ! phb )
2005-07-25 19:17:03 +04:00
return - EIO ;
2005-04-17 02:20:36 +04:00
2005-07-25 19:16:42 +04:00
if ( rpaphp_add_slot ( dn ) ) {
printk ( KERN_ERR " %s: unable to add hotplug slot %s \n " ,
__FUNCTION__ , drc_name ) ;
return - EIO ;
}
2005-04-17 02:20:36 +04:00
return 0 ;
}
2005-07-25 19:17:03 +04:00
static int dlpar_add_vio_slot ( char * drc_name , struct device_node * dn )
{
if ( vio_find_node ( dn ) )
return - EINVAL ;
if ( ! vio_register_device_node ( dn ) ) {
printk ( KERN_ERR
" %s: failed to register vio node %s \n " ,
__FUNCTION__ , drc_name ) ;
return - EIO ;
}
return 0 ;
}
2005-04-17 02:20:36 +04:00
/**
* dlpar_add_slot - DLPAR add an I / O Slot
* @ drc_name : drc - name of newly added slot
*
* Make the hotplug module and the kernel aware
* of a newly added I / O Slot .
* Return Codes -
* 0 Success
* - ENODEV Not a valid drc_name
* - EINVAL Slot already added
* - ERESTARTSYS Signalled before obtaining lock
* - EIO Internal PCI Error
*/
int dlpar_add_slot ( char * drc_name )
{
struct device_node * dn = NULL ;
int node_type ;
2005-07-25 19:17:03 +04:00
int rc = - EIO ;
2005-04-17 02:20:36 +04:00
if ( down_interruptible ( & rpadlpar_sem ) )
return - ERESTARTSYS ;
2005-07-25 19:16:42 +04:00
/* Find newly added node */
dn = find_dlpar_node ( drc_name , & node_type ) ;
2005-04-17 02:20:36 +04:00
if ( ! dn ) {
rc = - ENODEV ;
goto exit ;
}
switch ( node_type ) {
case NODE_TYPE_VIO :
2005-07-25 19:17:03 +04:00
rc = dlpar_add_vio_slot ( drc_name , dn ) ;
2005-04-17 02:20:36 +04:00
break ;
case NODE_TYPE_SLOT :
rc = dlpar_add_pci_slot ( drc_name , dn ) ;
break ;
case NODE_TYPE_PHB :
2005-07-25 19:16:42 +04:00
rc = dlpar_add_phb ( drc_name , dn ) ;
2005-04-17 02:20:36 +04:00
break ;
}
2005-07-25 19:17:03 +04:00
printk ( KERN_INFO " %s: slot %s added \n " , DLPAR_MODULE_NAME , drc_name ) ;
2005-04-17 02:20:36 +04:00
exit :
up ( & rpadlpar_sem ) ;
return rc ;
}
/**
* dlpar_remove_vio_slot - DLPAR remove a virtual I / O Slot
* @ drc_name : drc - name of newly added slot
*
* Remove the kernel and hotplug representations
* of an I / O Slot .
* Return Codes :
* 0 Success
2005-07-25 19:17:03 +04:00
* - EINVAL Vio dev doesn ' t exist
2005-04-17 02:20:36 +04:00
*/
2005-07-25 19:17:03 +04:00
static int dlpar_remove_vio_slot ( char * drc_name , struct device_node * dn )
2005-04-17 02:20:36 +04:00
{
2005-07-25 19:16:42 +04:00
struct vio_dev * vio_dev ;
2005-04-17 02:20:36 +04:00
2005-07-25 19:16:42 +04:00
vio_dev = vio_find_node ( dn ) ;
2005-07-25 19:17:03 +04:00
if ( ! vio_dev )
return - EINVAL ;
2005-07-25 19:16:42 +04:00
vio_unregister_device ( vio_dev ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
/**
* dlpar_remove_slot - DLPAR remove a PCI I / O Slot
* @ drc_name : drc - name of newly added slot
*
* Remove the kernel and hotplug representations
* of a PCI I / O Slot .
* Return Codes :
* 0 Success
* - ENODEV Not a valid drc_name
* - EIO Internal PCI Error
*/
2005-07-25 19:17:03 +04:00
int dlpar_remove_pci_slot ( char * drc_name , struct device_node * dn )
2005-04-17 02:20:36 +04:00
{
2005-07-25 19:17:03 +04:00
struct pci_bus * bus ;
struct slot * slot ;
2005-04-17 02:20:36 +04:00
2005-07-25 19:17:03 +04:00
bus = rpaphp_find_pci_bus ( dn ) ;
if ( ! bus )
return - EINVAL ;
slot = find_slot ( dn ) ;
if ( slot ) {
/* Remove hotplug slot */
if ( rpaphp_remove_slot ( slot ) ) {
printk ( KERN_ERR
" %s: unable to remove hotplug slot %s \n " ,
__FUNCTION__ , drc_name ) ;
return - EIO ;
}
2005-11-05 00:38:50 +03:00
} else {
rpaphp_unconfig_pci_adapter ( bus ) ;
2005-04-17 02:20:36 +04:00
}
2005-07-25 20:13:38 +04:00
if ( unmap_bus_range ( bus ) ) {
printk ( KERN_ERR " %s: failed to unmap bus range \n " ,
__FUNCTION__ ) ;
return - ERANGE ;
2005-04-17 02:20:36 +04:00
}
2005-07-25 20:13:38 +04:00
BUG_ON ( ! bus - > self ) ;
pci_remove_bus_device ( bus - > self ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
/**
* dlpar_remove_slot - DLPAR remove an I / O Slot
* @ drc_name : drc - name of newly added slot
*
* Remove the kernel and hotplug representations
* of an I / O Slot .
* Return Codes :
* 0 Success
* - ENODEV Not a valid drc_name
* - EINVAL Slot already removed
* - ERESTARTSYS Signalled before obtaining lock
* - EIO Internal Error
*/
int dlpar_remove_slot ( char * drc_name )
{
2005-07-25 19:16:42 +04:00
struct device_node * dn ;
int node_type ;
2005-04-17 02:20:36 +04:00
int rc = 0 ;
if ( down_interruptible ( & rpadlpar_sem ) )
return - ERESTARTSYS ;
2005-07-25 19:16:42 +04:00
dn = find_dlpar_node ( drc_name , & node_type ) ;
if ( ! dn ) {
2005-04-17 02:20:36 +04:00
rc = - ENODEV ;
goto exit ;
}
2005-07-25 19:17:03 +04:00
switch ( node_type ) {
case NODE_TYPE_VIO :
rc = dlpar_remove_vio_slot ( drc_name , dn ) ;
break ;
case NODE_TYPE_PHB :
rc = dlpar_remove_phb ( drc_name , dn ) ;
break ;
case NODE_TYPE_SLOT :
rc = dlpar_remove_pci_slot ( drc_name , dn ) ;
break ;
2005-04-17 02:20:36 +04:00
}
2005-07-25 19:17:03 +04:00
printk ( KERN_INFO " %s: slot %s removed \n " , DLPAR_MODULE_NAME , drc_name ) ;
2005-04-17 02:20:36 +04:00
exit :
up ( & rpadlpar_sem ) ;
return rc ;
}
static inline int is_dlpar_capable ( void )
{
int rc = rtas_token ( " ibm,configure-connector " ) ;
return ( int ) ( rc ! = RTAS_UNKNOWN_SERVICE ) ;
}
int __init rpadlpar_io_init ( void )
{
int rc = 0 ;
if ( ! is_dlpar_capable ( ) ) {
printk ( KERN_WARNING " %s: partition not DLPAR capable \n " ,
__FUNCTION__ ) ;
return - EPERM ;
}
rc = dlpar_sysfs_init ( ) ;
return rc ;
}
void rpadlpar_io_exit ( void )
{
dlpar_sysfs_exit ( ) ;
return ;
}
module_init ( rpadlpar_io_init ) ;
module_exit ( rpadlpar_io_exit ) ;
MODULE_LICENSE ( " GPL " ) ;