2007-07-10 09:22:44 +04:00
/* vio.c: Virtual I/O channel devices probing infrastructure.
*
* Copyright ( c ) 2003 - 2005 IBM Corp .
* Dave Engebretsen engebret @ us . ibm . com
* Santiago Leon santil @ us . ibm . com
* Hollis Blanchard < hollisb @ us . ibm . com >
* Stephen Rothwell
*
* Adapted to sparc64 by David S . Miller davem @ davemloft . net
*/
# include <linux/kernel.h>
# include <linux/irq.h>
# include <linux/init.h>
# include <asm/mdesc.h>
# include <asm/vio.h>
static const struct vio_device_id * vio_match_device (
const struct vio_device_id * matches ,
const struct vio_dev * dev )
{
const char * type , * compat ;
int len ;
type = dev - > type ;
compat = dev - > compat ;
len = dev - > compat_len ;
while ( matches - > type [ 0 ] | | matches - > compat [ 0 ] ) {
int match = 1 ;
2007-07-13 01:16:22 +04:00
if ( matches - > type [ 0 ] )
match & = ! strcmp ( matches - > type , type ) ;
2007-07-10 09:22:44 +04:00
if ( matches - > compat [ 0 ] ) {
2007-07-13 01:16:22 +04:00
match & = len & &
2007-08-08 05:46:36 +04:00
of_find_in_proplist ( compat , matches - > compat , len ) ;
2007-07-10 09:22:44 +04:00
}
if ( match )
return matches ;
matches + + ;
}
return NULL ;
}
static int vio_bus_match ( struct device * dev , struct device_driver * drv )
{
struct vio_dev * vio_dev = to_vio_dev ( dev ) ;
struct vio_driver * vio_drv = to_vio_driver ( drv ) ;
const struct vio_device_id * matches = vio_drv - > id_table ;
if ( ! matches )
return 0 ;
return vio_match_device ( matches , vio_dev ) ! = NULL ;
}
static int vio_device_probe ( struct device * dev )
{
struct vio_dev * vdev = to_vio_dev ( dev ) ;
struct vio_driver * drv = to_vio_driver ( dev - > driver ) ;
const struct vio_device_id * id ;
int error = - ENODEV ;
if ( drv - > probe ) {
id = vio_match_device ( drv - > id_table , vdev ) ;
if ( id )
error = drv - > probe ( vdev , id ) ;
}
return error ;
}
static int vio_device_remove ( struct device * dev )
{
struct vio_dev * vdev = to_vio_dev ( dev ) ;
struct vio_driver * drv = to_vio_driver ( dev - > driver ) ;
if ( drv - > remove )
return drv - > remove ( vdev ) ;
return 1 ;
}
static ssize_t devspec_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct vio_dev * vdev = to_vio_dev ( dev ) ;
const char * str = " none " ;
2007-07-19 01:37:26 +04:00
if ( ! strcmp ( vdev - > type , " vnet-port " ) )
2007-07-12 05:15:59 +04:00
str = " vnet " ;
2007-07-19 01:37:26 +04:00
else if ( ! strcmp ( vdev - > type , " vdc-port " ) )
2007-07-12 05:15:59 +04:00
str = " vdisk " ;
2007-07-10 09:22:44 +04:00
return sprintf ( buf , " %s \n " , str ) ;
}
2007-07-12 05:15:59 +04:00
static ssize_t type_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct vio_dev * vdev = to_vio_dev ( dev ) ;
return sprintf ( buf , " %s \n " , vdev - > type ) ;
}
2007-07-10 09:22:44 +04:00
static struct device_attribute vio_dev_attrs [ ] = {
__ATTR_RO ( devspec ) ,
2007-07-12 05:15:59 +04:00
__ATTR_RO ( type ) ,
2007-07-10 09:22:44 +04:00
__ATTR_NULL
} ;
static struct bus_type vio_bus_type = {
. name = " vio " ,
. dev_attrs = vio_dev_attrs ,
. match = vio_bus_match ,
. probe = vio_device_probe ,
. remove = vio_device_remove ,
} ;
int vio_register_driver ( struct vio_driver * viodrv )
{
viodrv - > driver . bus = & vio_bus_type ;
return driver_register ( & viodrv - > driver ) ;
}
EXPORT_SYMBOL ( vio_register_driver ) ;
void vio_unregister_driver ( struct vio_driver * viodrv )
{
driver_unregister ( & viodrv - > driver ) ;
}
EXPORT_SYMBOL ( vio_unregister_driver ) ;
2008-01-22 04:22:46 +03:00
static void vio_dev_release ( struct device * dev )
2007-07-10 09:22:44 +04:00
{
kfree ( to_vio_dev ( dev ) ) ;
}
static ssize_t
show_pciobppath_attr ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
struct vio_dev * vdev ;
struct device_node * dp ;
vdev = to_vio_dev ( dev ) ;
dp = vdev - > dp ;
return snprintf ( buf , PAGE_SIZE , " %s \n " , dp - > full_name ) ;
}
static DEVICE_ATTR ( obppath , S_IRUSR | S_IRGRP | S_IROTH ,
show_pciobppath_attr , NULL ) ;
2008-09-12 11:04:33 +04:00
static struct device_node * cdev_node ;
2007-07-10 09:22:44 +04:00
static struct vio_dev * root_vdev ;
static u64 cdev_cfg_handle ;
2007-07-13 00:47:50 +04:00
static void vio_fill_channel_info ( struct mdesc_handle * hp , u64 mp ,
struct vio_dev * vdev )
{
u64 a ;
mdesc_for_each_arc ( a , hp , mp , MDESC_ARC_TYPE_FWD ) {
const u64 * chan_id ;
const u64 * irq ;
u64 target ;
target = mdesc_arc_target ( hp , a ) ;
irq = mdesc_get_property ( hp , target , " tx-ino " , NULL ) ;
if ( irq )
vdev - > tx_irq = sun4v_build_virq ( cdev_cfg_handle , * irq ) ;
irq = mdesc_get_property ( hp , target , " rx-ino " , NULL ) ;
if ( irq )
vdev - > rx_irq = sun4v_build_virq ( cdev_cfg_handle , * irq ) ;
chan_id = mdesc_get_property ( hp , target , " id " , NULL ) ;
if ( chan_id )
vdev - > channel_id = * chan_id ;
}
}
static struct vio_dev * vio_create_one ( struct mdesc_handle * hp , u64 mp ,
2007-07-10 09:22:44 +04:00
struct device * parent )
{
2007-07-18 10:03:47 +04:00
const char * type , * compat , * bus_id_name ;
2007-07-10 09:22:44 +04:00
struct device_node * dp ;
struct vio_dev * vdev ;
2007-07-13 01:16:22 +04:00
int err , tlen , clen ;
2007-07-20 09:51:07 +04:00
const u64 * id , * cfg_handle ;
u64 a ;
2007-07-10 09:22:44 +04:00
2007-07-13 01:16:22 +04:00
type = mdesc_get_property ( hp , mp , " device-type " , & tlen ) ;
2007-07-12 05:15:59 +04:00
if ( ! type ) {
2007-07-13 01:16:22 +04:00
type = mdesc_get_property ( hp , mp , " name " , & tlen ) ;
if ( ! type ) {
2007-07-13 00:47:50 +04:00
type = mdesc_node_name ( hp , mp ) ;
2007-07-13 01:16:22 +04:00
tlen = strlen ( type ) + 1 ;
}
}
if ( tlen > VIO_MAX_TYPE_LEN ) {
printk ( KERN_ERR " VIO: Type string [%s] is too long. \n " ,
type ) ;
return NULL ;
2007-07-12 05:15:59 +04:00
}
2007-07-13 01:16:22 +04:00
2007-07-20 09:51:07 +04:00
id = mdesc_get_property ( hp , mp , " id " , NULL ) ;
2007-07-19 02:15:45 +04:00
2007-07-20 09:51:07 +04:00
cfg_handle = NULL ;
mdesc_for_each_arc ( a , hp , mp , MDESC_ARC_TYPE_BACK ) {
u64 target ;
2007-07-19 02:15:45 +04:00
2007-07-20 09:51:07 +04:00
target = mdesc_arc_target ( hp , a ) ;
cfg_handle = mdesc_get_property ( hp , target ,
2007-07-19 02:15:45 +04:00
" cfg-handle " , NULL ) ;
2007-07-20 09:51:07 +04:00
if ( cfg_handle )
break ;
}
2007-07-19 02:15:45 +04:00
2007-07-18 10:03:47 +04:00
bus_id_name = type ;
if ( ! strcmp ( type , " domain-services-port " ) )
bus_id_name = " ds " ;
2009-07-03 01:27:22 +04:00
/*
* 20 char is the old driver - core name size limit , which is no more .
* This check can probably be removed after review and possible
* adaption of the vio users name length handling .
*/
if ( strlen ( bus_id_name ) > = 20 - 4 ) {
2007-07-18 10:03:47 +04:00
printk ( KERN_ERR " VIO: bus_id_name [%s] is too long. \n " ,
bus_id_name ) ;
return NULL ;
}
2007-07-13 00:47:50 +04:00
compat = mdesc_get_property ( hp , mp , " device-type " , & clen ) ;
2007-07-13 01:16:22 +04:00
if ( ! compat ) {
clen = 0 ;
} else if ( clen > VIO_MAX_COMPAT_LEN ) {
printk ( KERN_ERR " VIO: Compat len %d for [%s] is too long. \n " ,
clen , type ) ;
return NULL ;
}
2007-07-10 09:22:44 +04:00
vdev = kzalloc ( sizeof ( * vdev ) , GFP_KERNEL ) ;
if ( ! vdev ) {
printk ( KERN_ERR " VIO: Could not allocate vio_dev \n " ) ;
return NULL ;
}
vdev - > mp = mp ;
2007-07-13 01:16:22 +04:00
memcpy ( vdev - > type , type , tlen ) ;
if ( compat )
memcpy ( vdev - > compat , compat , clen ) ;
else
memset ( vdev - > compat , 0 , sizeof ( vdev - > compat ) ) ;
2007-07-10 09:22:44 +04:00
vdev - > compat_len = clen ;
2007-07-13 00:47:50 +04:00
vdev - > channel_id = ~ 0UL ;
vdev - > tx_irq = ~ 0 ;
vdev - > rx_irq = ~ 0 ;
2007-07-10 09:22:44 +04:00
2007-07-13 00:47:50 +04:00
vio_fill_channel_info ( hp , mp , vdev ) ;
2007-07-10 09:22:44 +04:00
2007-07-19 02:15:45 +04:00
if ( ! id ) {
2008-05-02 08:02:41 +04:00
dev_set_name ( & vdev - > dev , " %s " , bus_id_name ) ;
2007-07-19 02:15:45 +04:00
vdev - > dev_no = ~ ( u64 ) 0 ;
2007-07-20 09:51:07 +04:00
} else if ( ! cfg_handle ) {
2009-01-07 00:19:28 +03:00
dev_set_name ( & vdev - > dev , " %s-%llu " , bus_id_name , * id ) ;
2007-07-19 02:15:45 +04:00
vdev - > dev_no = * id ;
2007-07-20 09:51:07 +04:00
} else {
2009-01-07 00:19:28 +03:00
dev_set_name ( & vdev - > dev , " %s-%llu-%llu " , bus_id_name ,
2008-05-02 08:02:41 +04:00
* cfg_handle , * id ) ;
2007-07-20 09:51:07 +04:00
vdev - > dev_no = * cfg_handle ;
2007-07-19 02:15:45 +04:00
}
2007-07-18 10:03:47 +04:00
2007-07-10 09:22:44 +04:00
vdev - > dev . parent = parent ;
vdev - > dev . bus = & vio_bus_type ;
vdev - > dev . release = vio_dev_release ;
if ( parent = = NULL ) {
dp = cdev_node ;
} else if ( to_vio_dev ( parent ) = = root_vdev ) {
dp = of_get_next_child ( cdev_node , NULL ) ;
while ( dp ) {
if ( ! strcmp ( dp - > type , type ) )
break ;
dp = of_get_next_child ( cdev_node , dp ) ;
}
} else {
dp = to_vio_dev ( parent ) - > dp ;
}
vdev - > dp = dp ;
2008-05-02 08:02:41 +04:00
printk ( KERN_INFO " VIO: Adding device %s \n " , dev_name ( & vdev - > dev ) ) ;
2007-07-18 10:03:47 +04:00
2007-07-10 09:22:44 +04:00
err = device_register ( & vdev - > dev ) ;
if ( err ) {
printk ( KERN_ERR " VIO: Could not register device %s, err=%d \n " ,
2008-05-02 08:02:41 +04:00
dev_name ( & vdev - > dev ) , err ) ;
2007-07-10 09:22:44 +04:00
kfree ( vdev ) ;
return NULL ;
}
if ( vdev - > dp )
err = sysfs_create_file ( & vdev - > dev . kobj ,
& dev_attr_obppath . attr ) ;
return vdev ;
}
2007-07-18 10:03:47 +04:00
static void vio_add ( struct mdesc_handle * hp , u64 node )
2007-07-10 09:22:44 +04:00
{
2007-07-18 10:03:47 +04:00
( void ) vio_create_one ( hp , node , & root_vdev - > dev ) ;
2007-07-10 09:22:44 +04:00
}
2007-07-18 10:03:47 +04:00
static int vio_md_node_match ( struct device * dev , void * arg )
2007-07-10 09:22:44 +04:00
{
2007-07-18 10:03:47 +04:00
struct vio_dev * vdev = to_vio_dev ( dev ) ;
2007-07-12 05:15:59 +04:00
2007-07-18 10:03:47 +04:00
if ( vdev - > mp = = ( u64 ) arg )
return 1 ;
2007-07-10 09:22:44 +04:00
2007-07-18 10:03:47 +04:00
return 0 ;
}
2007-07-12 05:15:59 +04:00
2007-07-18 10:03:47 +04:00
static void vio_remove ( struct mdesc_handle * hp , u64 node )
{
struct device * dev ;
2007-07-12 05:15:59 +04:00
2007-07-18 10:03:47 +04:00
dev = device_find_child ( & root_vdev - > dev , ( void * ) node ,
vio_md_node_match ) ;
if ( dev ) {
2008-05-02 08:02:41 +04:00
printk ( KERN_INFO " VIO: Removing device %s \n " , dev_name ( dev ) ) ;
2007-07-18 10:03:47 +04:00
device_unregister ( dev ) ;
2007-07-12 05:15:59 +04:00
}
2007-07-10 09:22:44 +04:00
}
2007-07-18 10:03:47 +04:00
static struct mdesc_notifier_client vio_device_notifier = {
. add = vio_add ,
. remove = vio_remove ,
. node_name = " virtual-device-port " ,
} ;
2007-10-04 08:08:11 +04:00
/* We are only interested in domain service ports under the
* " domain-services " node . On control nodes there is another port
* under " openboot " that we should not mess with as aparently that is
* reserved exclusively for OBP use .
*/
static void vio_add_ds ( struct mdesc_handle * hp , u64 node )
{
int found ;
u64 a ;
found = 0 ;
mdesc_for_each_arc ( a , hp , node , MDESC_ARC_TYPE_BACK ) {
u64 target = mdesc_arc_target ( hp , a ) ;
const char * name = mdesc_node_name ( hp , target ) ;
if ( ! strcmp ( name , " domain-services " ) ) {
found = 1 ;
break ;
}
}
if ( found )
( void ) vio_create_one ( hp , node , & root_vdev - > dev ) ;
}
2007-07-18 10:03:47 +04:00
static struct mdesc_notifier_client vio_ds_notifier = {
2007-10-04 08:08:11 +04:00
. add = vio_add_ds ,
2007-07-18 10:03:47 +04:00
. remove = vio_remove ,
. node_name = " domain-services-port " ,
} ;
2008-09-12 11:04:33 +04:00
static const char * channel_devices_node = " channel-devices " ;
static const char * channel_devices_compat = " SUNW,sun4v-channel-devices " ;
static const char * cfg_handle_prop = " cfg-handle " ;
2007-07-10 09:22:44 +04:00
static int __init vio_init ( void )
{
2007-07-13 00:47:50 +04:00
struct mdesc_handle * hp ;
2007-07-10 09:22:44 +04:00
const char * compat ;
const u64 * cfg_handle ;
int err , len ;
2007-07-13 00:47:50 +04:00
u64 root ;
2007-07-14 11:41:08 +04:00
err = bus_register ( & vio_bus_type ) ;
if ( err ) {
printk ( KERN_ERR " VIO: Could not register bus type err=%d \n " ,
err ) ;
return err ;
}
2007-07-13 00:47:50 +04:00
hp = mdesc_grab ( ) ;
if ( ! hp )
return 0 ;
2007-07-10 09:22:44 +04:00
2007-07-13 00:47:50 +04:00
root = mdesc_node_by_name ( hp , MDESC_NODE_NULL , channel_devices_node ) ;
if ( root = = MDESC_NODE_NULL ) {
2007-07-10 09:22:44 +04:00
printk ( KERN_INFO " VIO: No channel-devices MDESC node. \n " ) ;
2007-07-13 00:47:50 +04:00
mdesc_release ( hp ) ;
2007-07-10 09:22:44 +04:00
return 0 ;
}
cdev_node = of_find_node_by_name ( NULL , " channel-devices " ) ;
2007-07-13 00:47:50 +04:00
err = - ENODEV ;
2007-07-10 09:22:44 +04:00
if ( ! cdev_node ) {
printk ( KERN_INFO " VIO: No channel-devices OBP node. \n " ) ;
2007-07-13 00:47:50 +04:00
goto out_release ;
2007-07-10 09:22:44 +04:00
}
2007-07-13 00:47:50 +04:00
compat = mdesc_get_property ( hp , root , " compatible " , & len ) ;
2007-07-10 09:22:44 +04:00
if ( ! compat ) {
printk ( KERN_ERR " VIO: Channel devices lacks compatible "
" property \n " ) ;
2007-07-13 00:47:50 +04:00
goto out_release ;
2007-07-10 09:22:44 +04:00
}
2007-08-08 05:46:36 +04:00
if ( ! of_find_in_proplist ( compat , channel_devices_compat , len ) ) {
2007-07-10 09:22:44 +04:00
printk ( KERN_ERR " VIO: Channel devices node lacks (%s) "
" compat entry. \n " , channel_devices_compat ) ;
2007-07-13 00:47:50 +04:00
goto out_release ;
2007-07-10 09:22:44 +04:00
}
2007-07-13 00:47:50 +04:00
cfg_handle = mdesc_get_property ( hp , root , cfg_handle_prop , NULL ) ;
2007-07-10 09:22:44 +04:00
if ( ! cfg_handle ) {
printk ( KERN_ERR " VIO: Channel devices lacks %s property \n " ,
cfg_handle_prop ) ;
2007-07-13 00:47:50 +04:00
goto out_release ;
2007-07-10 09:22:44 +04:00
}
cdev_cfg_handle = * cfg_handle ;
2007-07-18 10:03:47 +04:00
root_vdev = vio_create_one ( hp , root , NULL ) ;
err = - ENODEV ;
if ( ! root_vdev ) {
printk ( KERN_ERR " VIO: Coult not create root device. \n " ) ;
goto out_release ;
}
2007-07-18 08:37:35 +04:00
mdesc_register_notifier ( & vio_device_notifier ) ;
mdesc_register_notifier ( & vio_ds_notifier ) ;
2007-07-13 00:47:50 +04:00
mdesc_release ( hp ) ;
2007-07-10 09:22:44 +04:00
2007-07-18 10:03:47 +04:00
return err ;
2007-07-13 00:47:50 +04:00
out_release :
mdesc_release ( hp ) ;
return err ;
2007-07-10 09:22:44 +04:00
}
postcore_initcall ( vio_init ) ;