2008-03-25 20:47:46 +03:00
/*
* kvm_virtio . c - virtio for kvm on s390
*
* Copyright IBM Corp . 2008
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License ( version 2 only )
* as published by the Free Software Foundation .
*
* Author ( s ) : Christian Borntraeger < borntraeger @ de . ibm . com >
*/
# include <linux/init.h>
# include <linux/bootmem.h>
# include <linux/err.h>
# include <linux/virtio.h>
# include <linux/virtio_config.h>
2008-06-20 17:24:18 +04:00
# include <linux/virtio_console.h>
2008-03-25 20:47:46 +03:00
# include <linux/interrupt.h>
# include <linux/virtio_ring.h>
2008-04-30 15:38:47 +04:00
# include <linux/pfn.h>
2008-03-25 20:47:46 +03:00
# include <asm/io.h>
# include <asm/kvm_para.h>
# include <asm/kvm_virtio.h>
# include <asm/setup.h>
# include <asm/s390_ext.h>
# define VIRTIO_SUBCODE_64 0x0D00
/*
* The pointer to our ( page ) of device descriptions .
*/
static void * kvm_devices ;
struct kvm_device {
struct virtio_device vdev ;
struct kvm_device_desc * desc ;
} ;
# define to_kvmdev(vd) container_of(vd, struct kvm_device, vdev)
/*
* memory layout :
* - kvm_device_descriptor
* struct kvm_device_desc
* - configuration
* struct kvm_vqconfig
* - feature bits
* - config space
*/
static struct kvm_vqconfig * kvm_vq_config ( const struct kvm_device_desc * desc )
{
return ( struct kvm_vqconfig * ) ( desc + 1 ) ;
}
static u8 * kvm_vq_features ( const struct kvm_device_desc * desc )
{
return ( u8 * ) ( kvm_vq_config ( desc ) + desc - > num_vq ) ;
}
static u8 * kvm_vq_configspace ( const struct kvm_device_desc * desc )
{
return kvm_vq_features ( desc ) + desc - > feature_len * 2 ;
}
/*
* The total size of the config page used by this device ( incl . desc )
*/
static unsigned desc_size ( const struct kvm_device_desc * desc )
{
return sizeof ( * desc )
+ desc - > num_vq * sizeof ( struct kvm_vqconfig )
+ desc - > feature_len * 2
+ desc - > config_len ;
}
2008-05-06 18:38:30 +04:00
/* This gets the device's feature bits. */
static u32 kvm_get_features ( struct virtio_device * vdev )
2008-03-25 20:47:46 +03:00
{
2008-05-06 18:38:30 +04:00
unsigned int i ;
u32 features = 0 ;
2008-03-25 20:47:46 +03:00
struct kvm_device_desc * desc = to_kvmdev ( vdev ) - > desc ;
2008-05-06 18:38:30 +04:00
u8 * in_features = kvm_vq_features ( desc ) ;
2008-03-25 20:47:46 +03:00
2008-05-06 18:38:30 +04:00
for ( i = 0 ; i < min ( desc - > feature_len * 8 , 32 ) ; i + + )
if ( in_features [ i / 8 ] & ( 1 < < ( i % 8 ) ) )
features | = ( 1 < < i ) ;
return features ;
}
2008-03-25 20:47:46 +03:00
2008-07-25 21:06:07 +04:00
static void kvm_finalize_features ( struct virtio_device * vdev )
2008-05-06 18:38:30 +04:00
{
2008-07-25 21:06:07 +04:00
unsigned int i , bits ;
2008-05-06 18:38:30 +04:00
struct kvm_device_desc * desc = to_kvmdev ( vdev ) - > desc ;
/* Second half of bitmap is features we accept. */
u8 * out_features = kvm_vq_features ( desc ) + desc - > feature_len ;
2008-03-25 20:47:46 +03:00
2008-05-06 18:38:30 +04:00
memset ( out_features , 0 , desc - > feature_len ) ;
2008-07-25 21:06:07 +04:00
bits = min_t ( unsigned , desc - > feature_len , sizeof ( vdev - > features ) ) * 8 ;
for ( i = 0 ; i < bits ; i + + ) {
if ( test_bit ( i , vdev - > features ) )
2008-05-06 18:38:30 +04:00
out_features [ i / 8 ] | = ( 1 < < ( i % 8 ) ) ;
}
2008-03-25 20:47:46 +03:00
}
/*
* Reading and writing elements in config space
*/
static void kvm_get ( struct virtio_device * vdev , unsigned int offset ,
void * buf , unsigned len )
{
struct kvm_device_desc * desc = to_kvmdev ( vdev ) - > desc ;
BUG_ON ( offset + len > desc - > config_len ) ;
memcpy ( buf , kvm_vq_configspace ( desc ) + offset , len ) ;
}
static void kvm_set ( struct virtio_device * vdev , unsigned int offset ,
const void * buf , unsigned len )
{
struct kvm_device_desc * desc = to_kvmdev ( vdev ) - > desc ;
BUG_ON ( offset + len > desc - > config_len ) ;
memcpy ( kvm_vq_configspace ( desc ) + offset , buf , len ) ;
}
/*
* The operations to get and set the status word just access
* the status field of the device descriptor . set_status will also
* make a hypercall to the host , to tell about status changes
*/
static u8 kvm_get_status ( struct virtio_device * vdev )
{
return to_kvmdev ( vdev ) - > desc - > status ;
}
static void kvm_set_status ( struct virtio_device * vdev , u8 status )
{
BUG_ON ( ! status ) ;
to_kvmdev ( vdev ) - > desc - > status = status ;
kvm_hypercall1 ( KVM_S390_VIRTIO_SET_STATUS ,
( unsigned long ) to_kvmdev ( vdev ) - > desc ) ;
}
/*
* To reset the device , we use the KVM_VIRTIO_RESET hypercall , using the
* descriptor address . The Host will zero the status and all the
* features .
*/
static void kvm_reset ( struct virtio_device * vdev )
{
kvm_hypercall1 ( KVM_S390_VIRTIO_RESET ,
( unsigned long ) to_kvmdev ( vdev ) - > desc ) ;
}
/*
* When the virtio_ring code wants to notify the Host , it calls us here and we
* make a hypercall . We hand the address of the virtqueue so the Host
* knows which virtqueue we ' re talking about .
*/
static void kvm_notify ( struct virtqueue * vq )
{
struct kvm_vqconfig * config = vq - > priv ;
kvm_hypercall1 ( KVM_S390_VIRTIO_NOTIFY , config - > address ) ;
}
/*
* This routine finds the first virtqueue described in the configuration of
* this device and sets it up .
*/
static struct virtqueue * kvm_find_vq ( struct virtio_device * vdev ,
unsigned index ,
void ( * callback ) ( struct virtqueue * vq ) )
{
struct kvm_device * kdev = to_kvmdev ( vdev ) ;
struct kvm_vqconfig * config ;
struct virtqueue * vq ;
int err ;
if ( index > = kdev - > desc - > num_vq )
return ERR_PTR ( - ENOENT ) ;
config = kvm_vq_config ( kdev - > desc ) + index ;
2008-04-30 15:38:47 +04:00
err = vmem_add_mapping ( config - > address ,
vring_size ( config - > num , PAGE_SIZE ) ) ;
if ( err )
2008-03-25 20:47:46 +03:00
goto out ;
vq = vring_new_virtqueue ( config - > num , vdev , ( void * ) config - > address ,
kvm_notify , callback ) ;
if ( ! vq ) {
err = - ENOMEM ;
goto unmap ;
}
/*
* register a callback token
* The host will sent this via the external interrupt parameter
*/
config - > token = ( u64 ) vq ;
vq - > priv = config ;
return vq ;
unmap :
2008-04-30 15:38:47 +04:00
vmem_remove_mapping ( config - > address ,
vring_size ( config - > num , PAGE_SIZE ) ) ;
2008-03-25 20:47:46 +03:00
out :
return ERR_PTR ( err ) ;
}
static void kvm_del_vq ( struct virtqueue * vq )
{
struct kvm_vqconfig * config = vq - > priv ;
vring_del_virtqueue ( vq ) ;
2008-04-30 15:38:47 +04:00
vmem_remove_mapping ( config - > address ,
vring_size ( config - > num , PAGE_SIZE ) ) ;
2008-03-25 20:47:46 +03:00
}
/*
* The config ops structure as defined by virtio config
*/
static struct virtio_config_ops kvm_vq_configspace_ops = {
2008-05-06 18:38:30 +04:00
. get_features = kvm_get_features ,
2008-07-25 21:06:07 +04:00
. finalize_features = kvm_finalize_features ,
2008-03-25 20:47:46 +03:00
. get = kvm_get ,
. set = kvm_set ,
. get_status = kvm_get_status ,
. set_status = kvm_set_status ,
. reset = kvm_reset ,
. find_vq = kvm_find_vq ,
. del_vq = kvm_del_vq ,
} ;
/*
* The root device for the kvm virtio devices .
* This makes them appear as / sys / devices / kvm_s390 / 0 , 1 , 2 not / sys / devices / 0 , 1 , 2.
*/
static struct device kvm_root = {
. parent = NULL ,
. bus_id = " kvm_s390 " ,
} ;
/*
* adds a new device and register it with virtio
* appropriate drivers are loaded by the device model
*/
2008-05-31 00:09:42 +04:00
static void add_kvm_device ( struct kvm_device_desc * d , unsigned int offset )
2008-03-25 20:47:46 +03:00
{
struct kvm_device * kdev ;
kdev = kzalloc ( sizeof ( * kdev ) , GFP_KERNEL ) ;
if ( ! kdev ) {
2008-05-31 00:09:42 +04:00
printk ( KERN_EMERG " Cannot allocate kvm dev %u type %u \n " ,
offset , d - > type ) ;
2008-03-25 20:47:46 +03:00
return ;
}
kdev - > vdev . dev . parent = & kvm_root ;
kdev - > vdev . id . device = d - > type ;
kdev - > vdev . config = & kvm_vq_configspace_ops ;
kdev - > desc = d ;
if ( register_virtio_device ( & kdev - > vdev ) ! = 0 ) {
2008-05-31 00:09:42 +04:00
printk ( KERN_ERR " Failed to register kvm device %u type %u \n " ,
offset , d - > type ) ;
2008-03-25 20:47:46 +03:00
kfree ( kdev ) ;
}
}
/*
* scan_devices ( ) simply iterates through the device page .
* The type 0 is reserved to mean " end of devices " .
*/
static void scan_devices ( void )
{
unsigned int i ;
struct kvm_device_desc * d ;
for ( i = 0 ; i < PAGE_SIZE ; i + = desc_size ( d ) ) {
d = kvm_devices + i ;
if ( d - > type = = 0 )
break ;
2008-05-31 00:09:42 +04:00
add_kvm_device ( d , i ) ;
2008-03-25 20:47:46 +03:00
}
}
/*
* we emulate the request_irq behaviour on top of s390 extints
*/
static void kvm_extint_handler ( u16 code )
{
void * data = ( void * ) * ( long * ) __LC_PFAULT_INTPARM ;
u16 subcode = S390_lowcore . cpu_addr ;
if ( ( subcode & 0xff00 ) ! = VIRTIO_SUBCODE_64 )
return ;
vring_interrupt ( 0 , data ) ;
}
/*
* Init function for virtio
* devices are in a single page above top of " normal " mem
*/
static int __init kvm_devices_init ( void )
{
int rc ;
if ( ! MACHINE_IS_KVM )
return - ENODEV ;
rc = device_register ( & kvm_root ) ;
if ( rc ) {
printk ( KERN_ERR " Could not register kvm_s390 root device " ) ;
return rc ;
}
2008-04-30 15:38:47 +04:00
rc = vmem_add_mapping ( PFN_PHYS ( max_pfn ) , PAGE_SIZE ) ;
if ( rc ) {
2008-03-25 20:47:46 +03:00
device_unregister ( & kvm_root ) ;
2008-04-30 15:38:47 +04:00
return rc ;
2008-03-25 20:47:46 +03:00
}
2008-04-30 15:38:47 +04:00
kvm_devices = ( void * ) PFN_PHYS ( max_pfn ) ;
2008-03-25 20:47:46 +03:00
ctl_set_bit ( 0 , 9 ) ;
register_external_interrupt ( 0x2603 , kvm_extint_handler ) ;
scan_devices ( ) ;
return 0 ;
}
2008-06-20 17:24:18 +04:00
/* code for early console output with virtio_console */
static __init int early_put_chars ( u32 vtermno , const char * buf , int count )
{
char scratch [ 17 ] ;
unsigned int len = count ;
if ( len > sizeof ( scratch ) - 1 )
len = sizeof ( scratch ) - 1 ;
scratch [ len ] = ' \0 ' ;
memcpy ( scratch , buf , len ) ;
kvm_hypercall1 ( KVM_S390_VIRTIO_NOTIFY , __pa ( scratch ) ) ;
return len ;
}
void s390_virtio_console_init ( void )
{
virtio_cons_early_init ( early_put_chars ) ;
}
2008-03-25 20:47:46 +03:00
/*
* We do this after core stuff , but before the drivers .
*/
postcore_initcall ( kvm_devices_init ) ;