2007-10-23 22:47:31 +04:00
/*
* The Guest 9 p transport driver
*
2008-02-07 04:25:58 +03:00
* This is a block based transport driver based on the lguest block driver
* code .
2007-10-23 22:47:31 +04:00
*
*/
/*
* Copyright ( C ) 2007 Eric Van Hensbergen , IBM Corporation
*
* Based on virtio console driver
* Copyright ( C ) 2006 , 2007 Rusty Russell , IBM Corporation
*
* 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 .
*
* 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 :
* Free Software Foundation
* 51 Franklin Street , Fifth Floor
* Boston , MA 02111 - 1301 USA
*
*/
# include <linux/in.h>
# include <linux/module.h>
# include <linux/net.h>
# include <linux/ipv6.h>
# include <linux/errno.h>
# include <linux/kernel.h>
# include <linux/un.h>
# include <linux/uaccess.h>
# include <linux/inet.h>
# include <linux/idr.h>
# include <linux/file.h>
# include <net/9p/9p.h>
# include <linux/parser.h>
# include <net/9p/transport.h>
# include <linux/scatterlist.h>
# include <linux/virtio.h>
# include <linux/virtio_9p.h>
2008-02-07 04:25:58 +03:00
# define VIRTQUEUE_NUM 128
2007-10-23 22:47:31 +04:00
/* a single mutex to manage channel initialization and attachment */
static DECLARE_MUTEX ( virtio_9p_lock ) ;
/* global which tracks highest initialized channel */
static int chan_index ;
2008-02-07 04:25:58 +03:00
# define P9_INIT_MAXTAG 16
# define REQ_STATUS_IDLE 0
# define REQ_STATUS_SENT 1
# define REQ_STATUS_RCVD 2
# define REQ_STATUS_FLSH 3
struct p9_req_t {
int status ;
wait_queue_head_t * wq ;
} ;
2007-10-23 22:47:31 +04:00
/* We keep all per-channel information in a structure.
* This structure is allocated within the devices dev - > mem space .
* A pointer to the structure will get put in the transport private .
*/
static struct virtio_chan {
bool initialized ; /* channel is initialized */
bool inuse ; /* channel is in use */
2008-02-07 04:25:58 +03:00
spinlock_t lock ;
2007-10-23 22:47:31 +04:00
struct virtio_device * vdev ;
2008-02-07 04:25:58 +03:00
struct virtqueue * vq ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
struct p9_idpool * tagpool ;
struct p9_req_t * reqs ;
int max_tag ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
/* Scatterlist: can be too big for stack. */
struct scatterlist sg [ VIRTQUEUE_NUM ] ;
2007-10-23 22:47:31 +04:00
} channels [ MAX_9P_CHAN ] ;
2008-02-07 04:25:58 +03:00
/* Lookup requests by tag */
static struct p9_req_t * p9_lookup_tag ( struct virtio_chan * c , u16 tag )
{
/* This looks up the original request by tag so we know which
* buffer to read the data into */
tag + + ;
while ( tag > = c - > max_tag ) {
int old_max = c - > max_tag ;
int count ;
if ( c - > max_tag )
c - > max_tag * = 2 ;
else
c - > max_tag = P9_INIT_MAXTAG ;
c - > reqs = krealloc ( c - > reqs , sizeof ( struct p9_req_t ) * c - > max_tag ,
GFP_ATOMIC ) ;
if ( ! c - > reqs ) {
printk ( KERN_ERR " Couldn't grow tag array \n " ) ;
BUG ( ) ;
}
for ( count = old_max ; count < c - > max_tag ; count + + ) {
c - > reqs [ count ] . status = REQ_STATUS_IDLE ;
c - > reqs [ count ] . wq = kmalloc ( sizeof ( wait_queue_t ) ,
GFP_ATOMIC ) ;
if ( ! c - > reqs [ count ] . wq ) {
printk ( KERN_ERR " Couldn't grow tag array \n " ) ;
BUG ( ) ;
}
init_waitqueue_head ( c - > reqs [ count ] . wq ) ;
}
}
return & c - > reqs [ tag ] ;
}
2007-10-23 22:47:31 +04:00
/* How many bytes left in this page. */
static unsigned int rest_of_page ( void * data )
{
return PAGE_SIZE - ( ( unsigned long ) data % PAGE_SIZE ) ;
}
2008-02-07 04:25:58 +03:00
static void p9_virtio_close ( struct p9_trans * trans )
{
struct virtio_chan * chan = trans - > priv ;
int count ;
unsigned int flags ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
spin_lock_irqsave ( & chan - > lock , flags ) ;
p9_idpool_destroy ( chan - > tagpool ) ;
for ( count = 0 ; count < chan - > max_tag ; count + + )
kfree ( chan - > reqs [ count ] . wq ) ;
kfree ( chan - > reqs ) ;
chan - > max_tag = 0 ;
spin_unlock_irqrestore ( & chan - > lock , flags ) ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
down ( & virtio_9p_lock ) ;
chan - > inuse = false ;
up ( & virtio_9p_lock ) ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
kfree ( trans ) ;
2007-10-23 22:47:31 +04:00
}
2008-02-07 04:25:58 +03:00
static void req_done ( struct virtqueue * vq )
2007-10-23 22:47:31 +04:00
{
2008-02-07 04:25:58 +03:00
struct virtio_chan * chan = vq - > vdev - > priv ;
struct p9_fcall * rc ;
unsigned int len ;
unsigned long flags ;
struct p9_req_t * req ;
spin_lock_irqsave ( & chan - > lock , flags ) ;
while ( ( rc = chan - > vq - > vq_ops - > get_buf ( chan - > vq , & len ) ) ! = NULL ) {
req = p9_lookup_tag ( chan , rc - > tag ) ;
req - > status = REQ_STATUS_RCVD ;
wake_up ( req - > wq ) ;
}
/* In case queue is stopped waiting for more buffers. */
spin_unlock_irqrestore ( & chan - > lock , flags ) ;
}
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
static int
pack_sg_list ( struct scatterlist * sg , int start , int limit , char * data ,
int count )
{
int s ;
int index = start ;
while ( count ) {
s = rest_of_page ( data ) ;
if ( s > count )
s = count ;
sg_set_buf ( & sg [ index + + ] , data , s ) ;
count - = s ;
data + = s ;
2008-02-18 05:42:53 +03:00
BUG_ON ( index > limit ) ;
2008-02-07 04:25:58 +03:00
}
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
return index - start ;
2007-10-23 22:47:31 +04:00
}
2008-02-07 04:25:58 +03:00
static int
2008-02-07 04:25:03 +03:00
p9_virtio_rpc ( struct p9_trans * t , struct p9_fcall * tc , struct p9_fcall * * rc )
2007-10-23 22:47:31 +04:00
{
2008-02-07 04:25:58 +03:00
int in , out ;
int n , err , size ;
struct virtio_chan * chan = t - > priv ;
char * rdata ;
struct p9_req_t * req ;
unsigned long flags ;
if ( * rc = = NULL ) {
2008-02-07 04:25:03 +03:00
* rc = kmalloc ( sizeof ( struct p9_fcall ) + t - > msize , GFP_KERNEL ) ;
2008-02-07 04:25:58 +03:00
if ( ! * rc )
return - ENOMEM ;
}
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
rdata = ( char * ) * rc + sizeof ( struct p9_fcall ) ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
n = P9_NOTAG ;
if ( tc - > id ! = P9_TVERSION ) {
n = p9_idpool_get ( chan - > tagpool ) ;
if ( n < 0 )
return - ENOMEM ;
2007-10-23 22:47:31 +04:00
}
2008-02-07 04:25:07 +03:00
spin_lock_irqsave ( & chan - > lock , flags ) ;
2008-02-07 04:25:58 +03:00
req = p9_lookup_tag ( chan , n ) ;
spin_unlock_irqrestore ( & chan - > lock , flags ) ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
p9_set_tag ( tc , n ) ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
P9_DPRINTK ( P9_DEBUG_TRANS , " 9p debug: virtio rpc tag %d \n " , n ) ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
out = pack_sg_list ( chan - > sg , 0 , VIRTQUEUE_NUM , tc - > sdata , tc - > size ) ;
2008-02-07 04:25:03 +03:00
in = pack_sg_list ( chan - > sg , out , VIRTQUEUE_NUM - out , rdata , t - > msize ) ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
req - > status = REQ_STATUS_SENT ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
if ( chan - > vq - > vq_ops - > add_buf ( chan - > vq , chan - > sg , out , in , tc ) ) {
P9_DPRINTK ( P9_DEBUG_TRANS ,
" 9p debug: virtio rpc add_buf returned failure " ) ;
return - EIO ;
}
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
chan - > vq - > vq_ops - > kick ( chan - > vq ) ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
wait_event ( * req - > wq , req - > status = = REQ_STATUS_RCVD ) ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
size = le32_to_cpu ( * ( __le32 * ) rdata ) ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:03 +03:00
err = p9_deserialize_fcall ( rdata , size , * rc , t - > extended ) ;
2008-02-07 04:25:58 +03:00
if ( err < 0 ) {
P9_DPRINTK ( P9_DEBUG_TRANS ,
" 9p debug: virtio rpc deserialize returned %d \n " , err ) ;
return err ;
}
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
# ifdef CONFIG_NET_9P_DEBUG
if ( ( p9_debug_level & P9_DEBUG_FCALL ) = = P9_DEBUG_FCALL ) {
char buf [ 150 ] ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:03 +03:00
p9_printfcall ( buf , sizeof ( buf ) , * rc , t - > extended ) ;
2008-02-07 04:25:58 +03:00
printk ( KERN_NOTICE " >>> %p %s \n " , t , buf ) ;
}
# endif
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
if ( n ! = P9_NOTAG & & p9_idpool_check ( n , chan - > tagpool ) )
p9_idpool_put ( n , chan - > tagpool ) ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
req - > status = REQ_STATUS_IDLE ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
return 0 ;
2007-10-23 22:47:31 +04:00
}
2008-02-07 04:25:58 +03:00
static int p9_virtio_probe ( struct virtio_device * vdev )
2007-10-23 22:47:31 +04:00
{
int err ;
struct virtio_chan * chan ;
int index ;
down ( & virtio_9p_lock ) ;
index = chan_index + + ;
chan = & channels [ index ] ;
up ( & virtio_9p_lock ) ;
if ( chan_index > MAX_9P_CHAN ) {
printk ( KERN_ERR " 9p: virtio: Maximum channels exceeded \n " ) ;
BUG ( ) ;
err = - ENOMEM ;
goto fail ;
}
2008-02-07 04:25:58 +03:00
chan - > vdev = vdev ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
/* We expect one virtqueue, for requests. */
chan - > vq = vdev - > config - > find_vq ( vdev , 0 , req_done ) ;
if ( IS_ERR ( chan - > vq ) ) {
err = PTR_ERR ( chan - > vq ) ;
goto out_free_vq ;
2007-10-23 22:47:31 +04:00
}
2008-02-07 04:25:58 +03:00
chan - > vq - > vdev - > priv = chan ;
spin_lock_init ( & chan - > lock ) ;
2007-10-23 22:47:31 +04:00
2008-02-07 04:25:58 +03:00
sg_init_table ( chan - > sg , VIRTQUEUE_NUM ) ;
2007-10-23 22:47:31 +04:00
chan - > inuse = false ;
chan - > initialized = true ;
return 0 ;
2008-02-07 04:25:58 +03:00
out_free_vq :
vdev - > config - > del_vq ( chan - > vq ) ;
2007-10-23 22:47:31 +04:00
fail :
down ( & virtio_9p_lock ) ;
chan_index - - ;
up ( & virtio_9p_lock ) ;
return err ;
}
/* This sets up a transport channel for 9p communication. Right now
* we only match the first available channel , but eventually we couldlook up
* alternate channels by matching devname versus a virtio_config entry .
* We use a simple reference count mechanism to ensure that only a single
* mount has a channel open at a time . */
2008-02-07 04:25:03 +03:00
static struct p9_trans *
p9_virtio_create ( const char * devname , char * args , int msize ,
unsigned char extended )
2007-10-23 22:47:31 +04:00
{
struct p9_trans * trans ;
struct virtio_chan * chan = channels ;
2008-02-07 04:25:58 +03:00
int index = 0 ;
2007-10-23 22:47:31 +04:00
down ( & virtio_9p_lock ) ;
while ( index < MAX_9P_CHAN ) {
if ( chan - > initialized & & ! chan - > inuse ) {
chan - > inuse = true ;
break ;
} else {
index + + ;
chan = & channels [ index ] ;
}
}
up ( & virtio_9p_lock ) ;
if ( index > = MAX_9P_CHAN ) {
2008-02-07 04:25:58 +03:00
printk ( KERN_ERR " 9p: no channels available \n " ) ;
return ERR_PTR ( - ENODEV ) ;
}
chan - > tagpool = p9_idpool_create ( ) ;
if ( IS_ERR ( chan - > tagpool ) ) {
printk ( KERN_ERR " 9p: couldn't allocate tagpool \n " ) ;
return ERR_PTR ( - ENOMEM ) ;
2007-10-23 22:47:31 +04:00
}
2008-02-07 04:25:58 +03:00
p9_idpool_get ( chan - > tagpool ) ; /* reserve tag 0 */
chan - > max_tag = 0 ;
chan - > reqs = NULL ;
2007-10-23 22:47:31 +04:00
trans = kmalloc ( sizeof ( struct p9_trans ) , GFP_KERNEL ) ;
if ( ! trans ) {
printk ( KERN_ERR " 9p: couldn't allocate transport \n " ) ;
return ERR_PTR ( - ENOMEM ) ;
}
2008-02-07 04:25:03 +03:00
trans - > extended = extended ;
trans - > msize = msize ;
2007-10-23 22:47:31 +04:00
trans - > close = p9_virtio_close ;
2008-02-07 04:25:58 +03:00
trans - > rpc = p9_virtio_rpc ;
2007-10-23 22:47:31 +04:00
trans - > priv = chan ;
return trans ;
}
2008-02-07 04:25:04 +03:00
static void p9_virtio_remove ( struct virtio_device * vdev )
{
struct virtio_chan * chan = vdev - > priv ;
BUG_ON ( chan - > inuse ) ;
if ( chan - > initialized ) {
vdev - > config - > del_vq ( chan - > vq ) ;
chan - > initialized = false ;
}
}
2007-10-23 22:47:31 +04:00
# define VIRTIO_ID_9P 9
static struct virtio_device_id id_table [ ] = {
{ VIRTIO_ID_9P , VIRTIO_DEV_ANY_ID } ,
{ 0 } ,
} ;
/* The standard "struct lguest_driver": */
static struct virtio_driver p9_virtio_drv = {
. driver . name = KBUILD_MODNAME ,
. driver . owner = THIS_MODULE ,
. id_table = id_table ,
. probe = p9_virtio_probe ,
2008-02-07 04:25:04 +03:00
. remove = p9_virtio_remove ,
2007-10-23 22:47:31 +04:00
} ;
static struct p9_trans_module p9_virtio_trans = {
. name = " virtio " ,
. create = p9_virtio_create ,
2008-02-07 04:25:58 +03:00
. maxsize = PAGE_SIZE * 16 ,
2007-10-23 22:47:31 +04:00
. def = 0 ,
} ;
/* The standard init function */
static int __init p9_virtio_init ( void )
{
int count ;
for ( count = 0 ; count < MAX_9P_CHAN ; count + + )
channels [ count ] . initialized = false ;
v9fs_register_trans ( & p9_virtio_trans ) ;
return register_virtio_driver ( & p9_virtio_drv ) ;
}
2008-02-07 04:25:04 +03:00
static void __exit p9_virtio_cleanup ( void )
{
unregister_virtio_driver ( & p9_virtio_drv ) ;
}
2007-10-23 22:47:31 +04:00
module_init ( p9_virtio_init ) ;
2008-02-07 04:25:04 +03:00
module_exit ( p9_virtio_cleanup ) ;
2007-10-23 22:47:31 +04:00
MODULE_DEVICE_TABLE ( virtio , id_table ) ;
MODULE_AUTHOR ( " Eric Van Hensbergen <ericvh@gmail.com> " ) ;
MODULE_DESCRIPTION ( " Virtio 9p Transport " ) ;
MODULE_LICENSE ( " GPL " ) ;