2007-10-11 14:57:26 +10:00
/*
* Legacy iSeries specific vio initialisation
* that needs to be built in ( not a module ) .
*
2007-10-12 15:37:32 +10:00
* © Copyright 2007 IBM Corporation
2007-10-11 14:57:26 +10:00
* Author : Stephen Rothwell
* Some parts collected from various other files
*
* 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 .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software Foundation ,
* Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/of.h>
# include <linux/init.h>
# include <linux/gfp.h>
# include <linux/completion.h>
# include <linux/proc_fs.h>
2007-10-11 14:59:54 +10:00
# include <linux/module.h>
2007-10-11 14:57:26 +10:00
# include <asm/firmware.h>
2007-10-11 14:59:54 +10:00
# include <asm/vio.h>
2007-10-11 14:57:26 +10:00
# include <asm/iseries/vio.h>
# include <asm/iseries/iommu.h>
# include <asm/iseries/hv_types.h>
# include <asm/iseries/hv_lp_event.h>
# define FIRST_VTY 0
# define NUM_VTYS 1
# define FIRST_VSCSI (FIRST_VTY + NUM_VTYS)
# define NUM_VSCSIS 1
# define FIRST_VLAN (FIRST_VSCSI + NUM_VSCSIS)
# define NUM_VLANS HVMAXARCHITECTEDVIRTUALLANS
# define FIRST_VIODASD (FIRST_VLAN + NUM_VLANS)
# define NUM_VIODASDS HVMAXARCHITECTEDVIRTUALDISKS
# define FIRST_VIOCD (FIRST_VIODASD + NUM_VIODASDS)
# define NUM_VIOCDS HVMAXARCHITECTEDVIRTUALCDROMS
# define FIRST_VIOTAPE (FIRST_VIOCD + NUM_VIOCDS)
# define NUM_VIOTAPES HVMAXARCHITECTEDVIRTUALTAPES
2007-10-11 14:58:31 +10:00
struct vio_waitevent {
struct completion com ;
int rc ;
u16 sub_result ;
} ;
struct vio_resource {
char rsrcname [ 10 ] ;
char type [ 4 ] ;
char model [ 3 ] ;
} ;
2007-10-11 14:59:54 +10:00
static struct property * new_property ( const char * name , int length ,
2007-10-11 14:57:26 +10:00
const void * value )
{
struct property * np = kzalloc ( sizeof ( * np ) + strlen ( name ) + 1 + length ,
GFP_KERNEL ) ;
if ( ! np )
return NULL ;
np - > name = ( char * ) ( np + 1 ) ;
np - > value = np - > name + strlen ( name ) + 1 ;
strcpy ( np - > name , name ) ;
memcpy ( np - > value , value , length ) ;
np - > length = length ;
return np ;
}
2008-02-14 08:30:55 +11:00
static void free_property ( struct property * np )
2007-10-11 14:57:26 +10:00
{
kfree ( np ) ;
}
2007-10-11 14:59:54 +10:00
static struct device_node * new_node ( const char * path ,
2007-10-11 14:57:26 +10:00
struct device_node * parent )
{
struct device_node * np = kzalloc ( sizeof ( * np ) , GFP_KERNEL ) ;
if ( ! np )
return NULL ;
np - > full_name = kmalloc ( strlen ( path ) + 1 , GFP_KERNEL ) ;
if ( ! np - > full_name ) {
kfree ( np ) ;
return NULL ;
}
strcpy ( np - > full_name , path ) ;
of_node_set_flag ( np , OF_DYNAMIC ) ;
kref_init ( & np - > kref ) ;
np - > parent = of_node_get ( parent ) ;
return np ;
}
2007-10-11 14:59:54 +10:00
static void free_node ( struct device_node * np )
2007-10-11 14:57:26 +10:00
{
struct property * next ;
struct property * prop ;
next = np - > properties ;
while ( next ) {
prop = next ;
next = prop - > next ;
free_property ( prop ) ;
}
of_node_put ( np - > parent ) ;
kfree ( np - > full_name ) ;
kfree ( np ) ;
}
2007-10-11 14:59:54 +10:00
static int add_string_property ( struct device_node * np , const char * name ,
2007-10-11 14:57:26 +10:00
const char * value )
{
struct property * nprop = new_property ( name , strlen ( value ) + 1 , value ) ;
if ( ! nprop )
return 0 ;
prom_add_property ( np , nprop ) ;
return 1 ;
}
2007-10-11 14:59:54 +10:00
static int add_raw_property ( struct device_node * np , const char * name ,
2007-10-11 14:57:26 +10:00
int length , const void * value )
{
struct property * nprop = new_property ( name , length , value ) ;
if ( ! nprop )
return 0 ;
prom_add_property ( np , nprop ) ;
return 1 ;
}
2007-10-11 14:59:54 +10:00
static struct device_node * do_device_node ( struct device_node * parent ,
const char * name , u32 reg , u32 unit , const char * type ,
const char * compat , struct vio_resource * res )
{
struct device_node * np ;
char path [ 32 ] ;
snprintf ( path , sizeof ( path ) , " /vdevice/%s@%08x " , name , reg ) ;
np = new_node ( path , parent ) ;
if ( ! np )
return NULL ;
if ( ! add_string_property ( np , " name " , name ) | |
! add_string_property ( np , " device_type " , type ) | |
! add_string_property ( np , " compatible " , compat ) | |
! add_raw_property ( np , " reg " , sizeof ( reg ) , & reg ) | |
! add_raw_property ( np , " linux,unit_address " ,
sizeof ( unit ) , & unit ) ) {
goto node_free ;
}
if ( res ) {
if ( ! add_raw_property ( np , " linux,vio_rsrcname " ,
sizeof ( res - > rsrcname ) , res - > rsrcname ) | |
! add_raw_property ( np , " linux,vio_type " ,
sizeof ( res - > type ) , res - > type ) | |
! add_raw_property ( np , " linux,vio_model " ,
sizeof ( res - > model ) , res - > model ) )
goto node_free ;
}
np - > name = of_get_property ( np , " name " , NULL ) ;
np - > type = of_get_property ( np , " device_type " , NULL ) ;
of_attach_node ( np ) ;
# ifdef CONFIG_PROC_DEVICETREE
if ( parent - > pde ) {
struct proc_dir_entry * ent ;
ent = proc_mkdir ( strrchr ( np - > full_name , ' / ' ) + 1 , parent - > pde ) ;
if ( ent )
proc_device_tree_add_node ( np , ent ) ;
}
# endif
return np ;
node_free :
free_node ( np ) ;
return NULL ;
}
/*
* This is here so that we can dynamically add viodasd
* devices without exposing all the above infrastructure .
*/
struct vio_dev * vio_create_viodasd ( u32 unit )
{
struct device_node * vio_root ;
struct device_node * np ;
struct vio_dev * vdev = NULL ;
vio_root = of_find_node_by_path ( " /vdevice " ) ;
if ( ! vio_root )
return NULL ;
np = do_device_node ( vio_root , " viodasd " , FIRST_VIODASD + unit , unit ,
" block " , " IBM,iSeries-viodasd " , NULL ) ;
of_node_put ( vio_root ) ;
if ( np ) {
vdev = vio_register_device_node ( np ) ;
if ( ! vdev )
free_node ( np ) ;
}
return vdev ;
}
EXPORT_SYMBOL_GPL ( vio_create_viodasd ) ;
static void __init handle_block_event ( struct HvLpEvent * event )
{
struct vioblocklpevent * bevent = ( struct vioblocklpevent * ) event ;
struct vio_waitevent * pwe ;
if ( event = = NULL )
/* Notification that a partition went away! */
return ;
/* First, we should NEVER get an int here...only acks */
if ( hvlpevent_is_int ( event ) ) {
printk ( KERN_WARNING " handle_viod_request: "
" Yikes! got an int in viodasd event handler! \n " ) ;
if ( hvlpevent_need_ack ( event ) ) {
event - > xRc = HvLpEvent_Rc_InvalidSubtype ;
HvCallEvent_ackLpEvent ( event ) ;
}
return ;
}
switch ( event - > xSubtype & VIOMINOR_SUBTYPE_MASK ) {
case vioblockopen :
/*
* Handle a response to an open request . We get all the
* disk information in the response , so update it . The
* correlation token contains a pointer to a waitevent
* structure that has a completion in it . update the
* return code in the waitevent structure and post the
* completion to wake up the guy who sent the request
*/
pwe = ( struct vio_waitevent * ) event - > xCorrelationToken ;
pwe - > rc = event - > xRc ;
pwe - > sub_result = bevent - > sub_result ;
complete ( & pwe - > com ) ;
break ;
case vioblockclose :
break ;
default :
printk ( KERN_WARNING " handle_viod_request: unexpected subtype! " ) ;
if ( hvlpevent_need_ack ( event ) ) {
event - > xRc = HvLpEvent_Rc_InvalidSubtype ;
HvCallEvent_ackLpEvent ( event ) ;
}
}
}
static void __init probe_disk ( struct device_node * vio_root , u32 unit )
{
HvLpEvent_Rc hvrc ;
struct vio_waitevent we ;
u16 flags = 0 ;
retry :
init_completion ( & we . com ) ;
/* Send the open event to OS/400 */
hvrc = HvCallEvent_signalLpEventFast ( viopath_hostLp ,
HvLpEvent_Type_VirtualIo ,
viomajorsubtype_blockio | vioblockopen ,
HvLpEvent_AckInd_DoAck , HvLpEvent_AckType_ImmediateAck ,
viopath_sourceinst ( viopath_hostLp ) ,
viopath_targetinst ( viopath_hostLp ) ,
( u64 ) ( unsigned long ) & we , VIOVERSION < < 16 ,
( ( u64 ) unit < < 48 ) | ( ( u64 ) flags < < 32 ) ,
0 , 0 , 0 ) ;
if ( hvrc ! = 0 ) {
printk ( KERN_WARNING " probe_disk: bad rc on HV open %d \n " ,
( int ) hvrc ) ;
return ;
}
wait_for_completion ( & we . com ) ;
if ( we . rc ! = 0 ) {
if ( flags ! = 0 )
return ;
/* try again with read only flag set */
flags = vioblockflags_ro ;
goto retry ;
}
/* Send the close event to OS/400. We DON'T expect a response */
hvrc = HvCallEvent_signalLpEventFast ( viopath_hostLp ,
HvLpEvent_Type_VirtualIo ,
viomajorsubtype_blockio | vioblockclose ,
HvLpEvent_AckInd_NoAck , HvLpEvent_AckType_ImmediateAck ,
viopath_sourceinst ( viopath_hostLp ) ,
viopath_targetinst ( viopath_hostLp ) ,
0 , VIOVERSION < < 16 ,
( ( u64 ) unit < < 48 ) | ( ( u64 ) flags < < 32 ) ,
0 , 0 , 0 ) ;
if ( hvrc ! = 0 ) {
printk ( KERN_WARNING " probe_disk: "
" bad rc sending event to OS/400 %d \n " , ( int ) hvrc ) ;
return ;
}
do_device_node ( vio_root , " viodasd " , FIRST_VIODASD + unit , unit ,
" block " , " IBM,iSeries-viodasd " , NULL ) ;
}
static void __init get_viodasd_info ( struct device_node * vio_root )
{
int rc ;
u32 unit ;
rc = viopath_open ( viopath_hostLp , viomajorsubtype_blockio , 2 ) ;
if ( rc ) {
printk ( KERN_WARNING " get_viodasd_info: "
" error opening path to host partition %d \n " ,
viopath_hostLp ) ;
return ;
}
/* Initialize our request handler */
vio_setHandler ( viomajorsubtype_blockio , handle_block_event ) ;
for ( unit = 0 ; unit < HVMAXARCHITECTEDVIRTUALDISKS ; unit + + )
probe_disk ( vio_root , unit ) ;
vio_clearHandler ( viomajorsubtype_blockio ) ;
viopath_close ( viopath_hostLp , viomajorsubtype_blockio , 2 ) ;
}
2007-10-11 14:57:26 +10:00
static void __init handle_cd_event ( struct HvLpEvent * event )
{
struct viocdlpevent * bevent ;
2007-10-11 14:58:31 +10:00
struct vio_waitevent * pwe ;
2007-10-11 14:57:26 +10:00
if ( ! event )
/* Notification that a partition went away! */
return ;
/* First, we should NEVER get an int here...only acks */
if ( hvlpevent_is_int ( event ) ) {
printk ( KERN_WARNING " handle_cd_event: got an unexpected int \n " ) ;
if ( hvlpevent_need_ack ( event ) ) {
event - > xRc = HvLpEvent_Rc_InvalidSubtype ;
HvCallEvent_ackLpEvent ( event ) ;
}
return ;
}
bevent = ( struct viocdlpevent * ) event ;
switch ( event - > xSubtype & VIOMINOR_SUBTYPE_MASK ) {
case viocdgetinfo :
2007-10-11 14:58:31 +10:00
pwe = ( struct vio_waitevent * ) event - > xCorrelationToken ;
2007-10-11 14:57:26 +10:00
pwe - > rc = event - > xRc ;
pwe - > sub_result = bevent - > sub_result ;
complete ( & pwe - > com ) ;
break ;
default :
printk ( KERN_WARNING " handle_cd_event: "
" message with unexpected subtype %0x04X! \n " ,
event - > xSubtype & VIOMINOR_SUBTYPE_MASK ) ;
if ( hvlpevent_need_ack ( event ) ) {
event - > xRc = HvLpEvent_Rc_InvalidSubtype ;
HvCallEvent_ackLpEvent ( event ) ;
}
}
}
static void __init get_viocd_info ( struct device_node * vio_root )
{
HvLpEvent_Rc hvrc ;
u32 unit ;
2007-10-11 14:58:31 +10:00
struct vio_waitevent we ;
struct vio_resource * unitinfo ;
2007-10-11 14:57:26 +10:00
dma_addr_t unitinfo_dmaaddr ;
int ret ;
ret = viopath_open ( viopath_hostLp , viomajorsubtype_cdio , 2 ) ;
if ( ret ) {
printk ( KERN_WARNING
" get_viocd_info: error opening path to host partition %d \n " ,
viopath_hostLp ) ;
return ;
}
/* Initialize our request handler */
vio_setHandler ( viomajorsubtype_cdio , handle_cd_event ) ;
unitinfo = iseries_hv_alloc (
sizeof ( * unitinfo ) * HVMAXARCHITECTEDVIRTUALCDROMS ,
& unitinfo_dmaaddr , GFP_ATOMIC ) ;
if ( ! unitinfo ) {
printk ( KERN_WARNING
" get_viocd_info: error allocating unitinfo \n " ) ;
goto clear_handler ;
}
memset ( unitinfo , 0 , sizeof ( * unitinfo ) * HVMAXARCHITECTEDVIRTUALCDROMS ) ;
init_completion ( & we . com ) ;
hvrc = HvCallEvent_signalLpEventFast ( viopath_hostLp ,
HvLpEvent_Type_VirtualIo ,
viomajorsubtype_cdio | viocdgetinfo ,
HvLpEvent_AckInd_DoAck , HvLpEvent_AckType_ImmediateAck ,
viopath_sourceinst ( viopath_hostLp ) ,
viopath_targetinst ( viopath_hostLp ) ,
( u64 ) & we , VIOVERSION < < 16 , unitinfo_dmaaddr , 0 ,
sizeof ( * unitinfo ) * HVMAXARCHITECTEDVIRTUALCDROMS , 0 ) ;
if ( hvrc ! = HvLpEvent_Rc_Good ) {
printk ( KERN_WARNING
" get_viocd_info: cdrom error sending event. rc %d \n " ,
( int ) hvrc ) ;
goto hv_free ;
}
wait_for_completion ( & we . com ) ;
if ( we . rc ) {
printk ( KERN_WARNING " get_viocd_info: bad rc %d:0x%04X \n " ,
we . rc , we . sub_result ) ;
goto hv_free ;
}
for ( unit = 0 ; ( unit < HVMAXARCHITECTEDVIRTUALCDROMS ) & &
unitinfo [ unit ] . rsrcname [ 0 ] ; unit + + ) {
2007-10-11 14:59:54 +10:00
if ( ! do_device_node ( vio_root , " viocd " , FIRST_VIOCD + unit , unit ,
" block " , " IBM,iSeries-viocd " , & unitinfo [ unit ] ) )
break ;
2007-10-11 14:57:26 +10:00
}
hv_free :
iseries_hv_free ( sizeof ( * unitinfo ) * HVMAXARCHITECTEDVIRTUALCDROMS ,
unitinfo , unitinfo_dmaaddr ) ;
clear_handler :
vio_clearHandler ( viomajorsubtype_cdio ) ;
viopath_close ( viopath_hostLp , viomajorsubtype_cdio , 2 ) ;
}
2007-10-11 14:58:31 +10:00
/* Handle interrupt events for tape */
static void __init handle_tape_event ( struct HvLpEvent * event )
{
struct vio_waitevent * we ;
struct viotapelpevent * tevent = ( struct viotapelpevent * ) event ;
if ( event = = NULL )
/* Notification that a partition went away! */
return ;
we = ( struct vio_waitevent * ) event - > xCorrelationToken ;
switch ( event - > xSubtype & VIOMINOR_SUBTYPE_MASK ) {
case viotapegetinfo :
we - > rc = tevent - > sub_type_result ;
complete ( & we - > com ) ;
break ;
default :
printk ( KERN_WARNING " handle_tape_event: weird ack \n " ) ;
}
}
static void __init get_viotape_info ( struct device_node * vio_root )
{
HvLpEvent_Rc hvrc ;
u32 unit ;
struct vio_resource * unitinfo ;
dma_addr_t unitinfo_dmaaddr ;
size_t len = sizeof ( * unitinfo ) * HVMAXARCHITECTEDVIRTUALTAPES ;
struct vio_waitevent we ;
int ret ;
ret = viopath_open ( viopath_hostLp , viomajorsubtype_tape , 2 ) ;
if ( ret ) {
printk ( KERN_WARNING " get_viotape_info: "
" error on viopath_open to hostlp %d \n " , ret ) ;
return ;
}
vio_setHandler ( viomajorsubtype_tape , handle_tape_event ) ;
unitinfo = iseries_hv_alloc ( len , & unitinfo_dmaaddr , GFP_ATOMIC ) ;
if ( ! unitinfo )
goto clear_handler ;
memset ( unitinfo , 0 , len ) ;
hvrc = HvCallEvent_signalLpEventFast ( viopath_hostLp ,
HvLpEvent_Type_VirtualIo ,
viomajorsubtype_tape | viotapegetinfo ,
HvLpEvent_AckInd_DoAck , HvLpEvent_AckType_ImmediateAck ,
viopath_sourceinst ( viopath_hostLp ) ,
viopath_targetinst ( viopath_hostLp ) ,
( u64 ) ( unsigned long ) & we , VIOVERSION < < 16 ,
unitinfo_dmaaddr , len , 0 , 0 ) ;
if ( hvrc ! = HvLpEvent_Rc_Good ) {
printk ( KERN_WARNING " get_viotape_info: hv error on op %d \n " ,
( int ) hvrc ) ;
goto hv_free ;
}
wait_for_completion ( & we . com ) ;
for ( unit = 0 ; ( unit < HVMAXARCHITECTEDVIRTUALTAPES ) & &
unitinfo [ unit ] . rsrcname [ 0 ] ; unit + + ) {
2007-10-11 14:59:54 +10:00
if ( ! do_device_node ( vio_root , " viotape " , FIRST_VIOTAPE + unit ,
unit , " byte " , " IBM,iSeries-viotape " ,
& unitinfo [ unit ] ) )
break ;
2007-10-11 14:58:31 +10:00
}
hv_free :
iseries_hv_free ( len , unitinfo , unitinfo_dmaaddr ) ;
clear_handler :
vio_clearHandler ( viomajorsubtype_tape ) ;
viopath_close ( viopath_hostLp , viomajorsubtype_tape , 2 ) ;
}
2007-10-11 14:57:26 +10:00
static int __init iseries_vio_init ( void )
{
struct device_node * vio_root ;
2007-11-06 17:26:42 +11:00
int ret = - ENODEV ;
2007-10-11 14:57:26 +10:00
if ( ! firmware_has_feature ( FW_FEATURE_ISERIES ) )
2007-11-06 17:26:42 +11:00
goto out ;
2007-10-11 14:57:26 +10:00
iommu_vio_init ( ) ;
vio_root = of_find_node_by_path ( " /vdevice " ) ;
if ( ! vio_root )
2007-11-06 17:26:42 +11:00
goto out ;
2007-10-11 14:57:26 +10:00
if ( viopath_hostLp = = HvLpIndexInvalid ) {
vio_set_hostlp ( ) ;
/* If we don't have a host, bail out */
if ( viopath_hostLp = = HvLpIndexInvalid )
goto put_node ;
}
2007-10-11 14:59:54 +10:00
get_viodasd_info ( vio_root ) ;
2007-10-11 14:57:26 +10:00
get_viocd_info ( vio_root ) ;
2007-10-11 14:58:31 +10:00
get_viotape_info ( vio_root ) ;
2007-10-11 14:57:26 +10:00
2007-11-06 17:26:42 +11:00
ret = 0 ;
2007-10-11 14:57:26 +10:00
put_node :
of_node_put ( vio_root ) ;
2007-11-06 17:26:42 +11:00
out :
return ret ;
2007-10-11 14:57:26 +10:00
}
arch_initcall ( iseries_vio_init ) ;