2007-10-22 05:03:38 +04:00
//#define DEBUG
# include <linux/spinlock.h>
# include <linux/blkdev.h>
# include <linux/hdreg.h>
# include <linux/virtio.h>
# include <linux/virtio_blk.h>
2007-10-24 15:21:21 +04:00
# include <linux/scatterlist.h>
# define VIRTIO_MAX_SG (3+MAX_PHYS_SEGMENTS)
2008-01-31 17:53:53 +03:00
# define PART_BITS 4
2007-10-22 05:03:38 +04:00
2008-02-01 11:05:00 +03:00
static int major , index ;
2008-01-31 17:53:53 +03:00
2007-10-22 05:03:38 +04:00
struct virtio_blk
{
spinlock_t lock ;
struct virtio_device * vdev ;
struct virtqueue * vq ;
/* The disk structure for the kernel. */
struct gendisk * disk ;
/* Request tracking. */
struct list_head reqs ;
mempool_t * pool ;
/* Scatterlist: can be too big for stack. */
2007-10-24 15:21:21 +04:00
struct scatterlist sg [ VIRTIO_MAX_SG ] ;
2007-10-22 05:03:38 +04:00
} ;
struct virtblk_req
{
struct list_head list ;
struct request * req ;
struct virtio_blk_outhdr out_hdr ;
2008-05-03 06:50:45 +04:00
u8 status ;
2007-10-22 05:03:38 +04:00
} ;
2008-02-05 07:49:57 +03:00
static void blk_done ( struct virtqueue * vq )
2007-10-22 05:03:38 +04:00
{
struct virtio_blk * vblk = vq - > vdev - > priv ;
struct virtblk_req * vbr ;
unsigned int len ;
unsigned long flags ;
spin_lock_irqsave ( & vblk - > lock , flags ) ;
while ( ( vbr = vblk - > vq - > vq_ops - > get_buf ( vblk - > vq , & len ) ) ! = NULL ) {
int uptodate ;
2008-05-03 06:50:45 +04:00
switch ( vbr - > status ) {
2007-10-22 05:03:38 +04:00
case VIRTIO_BLK_S_OK :
uptodate = 1 ;
break ;
case VIRTIO_BLK_S_UNSUPP :
uptodate = - ENOTTY ;
break ;
default :
uptodate = 0 ;
break ;
}
end_dequeued_request ( vbr - > req , uptodate ) ;
list_del ( & vbr - > list ) ;
mempool_free ( vbr , vblk - > pool ) ;
}
/* In case queue is stopped waiting for more buffers. */
blk_start_queue ( vblk - > disk - > queue ) ;
spin_unlock_irqrestore ( & vblk - > lock , flags ) ;
}
static bool do_req ( struct request_queue * q , struct virtio_blk * vblk ,
struct request * req )
{
unsigned long num , out , in ;
struct virtblk_req * vbr ;
vbr = mempool_alloc ( vblk - > pool , GFP_ATOMIC ) ;
if ( ! vbr )
/* When another request finishes we'll try again. */
return false ;
vbr - > req = req ;
if ( blk_fs_request ( vbr - > req ) ) {
vbr - > out_hdr . type = 0 ;
vbr - > out_hdr . sector = vbr - > req - > sector ;
vbr - > out_hdr . ioprio = vbr - > req - > ioprio ;
} else if ( blk_pc_request ( vbr - > req ) ) {
vbr - > out_hdr . type = VIRTIO_BLK_T_SCSI_CMD ;
vbr - > out_hdr . sector = 0 ;
vbr - > out_hdr . ioprio = vbr - > req - > ioprio ;
} else {
/* We don't put anything else in the queue. */
BUG ( ) ;
}
if ( blk_barrier_rq ( vbr - > req ) )
vbr - > out_hdr . type | = VIRTIO_BLK_T_BARRIER ;
2007-10-24 15:21:21 +04:00
/* This init could be done at vblk creation time */
sg_init_table ( vblk - > sg , VIRTIO_MAX_SG ) ;
2007-10-22 05:03:38 +04:00
sg_set_buf ( & vblk - > sg [ 0 ] , & vbr - > out_hdr , sizeof ( vbr - > out_hdr ) ) ;
num = blk_rq_map_sg ( q , vbr - > req , vblk - > sg + 1 ) ;
2008-05-03 06:50:45 +04:00
sg_set_buf ( & vblk - > sg [ num + 1 ] , & vbr - > status , sizeof ( vbr - > status ) ) ;
2007-10-22 05:03:38 +04:00
if ( rq_data_dir ( vbr - > req ) = = WRITE ) {
vbr - > out_hdr . type | = VIRTIO_BLK_T_OUT ;
out = 1 + num ;
in = 1 ;
} else {
vbr - > out_hdr . type | = VIRTIO_BLK_T_IN ;
out = 1 ;
in = 1 + num ;
}
if ( vblk - > vq - > vq_ops - > add_buf ( vblk - > vq , vblk - > sg , out , in , vbr ) ) {
mempool_free ( vbr , vblk - > pool ) ;
return false ;
}
list_add_tail ( & vbr - > list , & vblk - > reqs ) ;
return true ;
}
static void do_virtblk_request ( struct request_queue * q )
{
struct virtio_blk * vblk = NULL ;
struct request * req ;
unsigned int issued = 0 ;
while ( ( req = elv_next_request ( q ) ) ! = NULL ) {
vblk = req - > rq_disk - > private_data ;
BUG_ON ( req - > nr_phys_segments > ARRAY_SIZE ( vblk - > sg ) ) ;
/* If this request fails, stop queue and wait for something to
finish to restart it . */
if ( ! do_req ( q , vblk , req ) ) {
blk_stop_queue ( q ) ;
break ;
}
blkdev_dequeue_request ( req ) ;
issued + + ;
}
if ( issued )
vblk - > vq - > vq_ops - > kick ( vblk - > vq ) ;
}
static int virtblk_ioctl ( struct inode * inode , struct file * filp ,
unsigned cmd , unsigned long data )
{
return scsi_cmd_ioctl ( filp , inode - > i_bdev - > bd_disk - > queue ,
inode - > i_bdev - > bd_disk , cmd ,
( void __user * ) data ) ;
}
2008-01-23 19:56:50 +03:00
/* We provide getgeo only to please some old bootloader/partitioning tools */
static int virtblk_getgeo ( struct block_device * bd , struct hd_geometry * geo )
{
/* some standard values, similar to sd */
geo - > heads = 1 < < 6 ;
geo - > sectors = 1 < < 5 ;
geo - > cylinders = get_capacity ( bd - > bd_disk ) > > 11 ;
return 0 ;
}
2007-10-22 05:03:38 +04:00
static struct block_device_operations virtblk_fops = {
2008-01-23 19:56:50 +03:00
. ioctl = virtblk_ioctl ,
. owner = THIS_MODULE ,
. getgeo = virtblk_getgeo ,
2007-10-22 05:03:38 +04:00
} ;
2008-02-01 11:05:00 +03:00
static int index_to_minor ( int index )
{
return index < < PART_BITS ;
}
2007-10-22 05:03:38 +04:00
static int virtblk_probe ( struct virtio_device * vdev )
{
struct virtio_blk * vblk ;
2008-01-31 17:53:53 +03:00
int err ;
2007-10-22 05:03:38 +04:00
u64 cap ;
u32 v ;
2008-02-01 11:05:00 +03:00
if ( index_to_minor ( index ) > = 1 < < MINORBITS )
2008-01-31 17:53:53 +03:00
return - ENOSPC ;
2007-10-22 05:03:38 +04:00
vdev - > priv = vblk = kmalloc ( sizeof ( * vblk ) , GFP_KERNEL ) ;
if ( ! vblk ) {
err = - ENOMEM ;
goto out ;
}
INIT_LIST_HEAD ( & vblk - > reqs ) ;
spin_lock_init ( & vblk - > lock ) ;
vblk - > vdev = vdev ;
/* We expect one virtqueue, for output. */
2008-02-05 07:49:56 +03:00
vblk - > vq = vdev - > config - > find_vq ( vdev , 0 , blk_done ) ;
2007-10-22 05:03:38 +04:00
if ( IS_ERR ( vblk - > vq ) ) {
err = PTR_ERR ( vblk - > vq ) ;
goto out_free_vblk ;
}
vblk - > pool = mempool_create_kmalloc_pool ( 1 , sizeof ( struct virtblk_req ) ) ;
if ( ! vblk - > pool ) {
err = - ENOMEM ;
goto out_free_vq ;
}
/* FIXME: How many partitions? How long is a piece of string? */
2008-01-31 17:53:53 +03:00
vblk - > disk = alloc_disk ( 1 < < PART_BITS ) ;
2007-10-22 05:03:38 +04:00
if ( ! vblk - > disk ) {
err = - ENOMEM ;
2008-01-31 17:53:53 +03:00
goto out_mempool ;
2007-10-22 05:03:38 +04:00
}
vblk - > disk - > queue = blk_init_queue ( do_virtblk_request , & vblk - > lock ) ;
if ( ! vblk - > disk - > queue ) {
err = - ENOMEM ;
goto out_put_disk ;
}
2008-02-01 11:05:00 +03:00
if ( index < 26 ) {
sprintf ( vblk - > disk - > disk_name , " vd%c " , ' a ' + index % 26 ) ;
} else if ( index < ( 26 + 1 ) * 26 ) {
sprintf ( vblk - > disk - > disk_name , " vd%c%c " ,
' a ' + index / 26 - 1 , ' a ' + index % 26 ) ;
} else {
const unsigned int m1 = ( index / 26 - 1 ) / 26 - 1 ;
const unsigned int m2 = ( index / 26 - 1 ) % 26 ;
const unsigned int m3 = index % 26 ;
sprintf ( vblk - > disk - > disk_name , " vd%c%c%c " ,
' a ' + m1 , ' a ' + m2 , ' a ' + m3 ) ;
}
2007-10-22 05:03:38 +04:00
vblk - > disk - > major = major ;
2008-02-01 11:05:00 +03:00
vblk - > disk - > first_minor = index_to_minor ( index ) ;
2007-10-22 05:03:38 +04:00
vblk - > disk - > private_data = vblk ;
vblk - > disk - > fops = & virtblk_fops ;
2008-03-03 01:00:15 +03:00
vblk - > disk - > driverfs_dev = & vdev - > dev ;
2008-02-01 11:05:00 +03:00
index + + ;
2008-01-31 17:53:53 +03:00
2007-10-22 05:03:38 +04:00
/* If barriers are supported, tell block layer that queue is ordered */
2008-05-03 06:50:50 +04:00
if ( virtio_has_feature ( vdev , VIRTIO_BLK_F_BARRIER ) )
2007-10-22 05:03:38 +04:00
blk_queue_ordered ( vblk - > disk - > queue , QUEUE_ORDERED_TAG , NULL ) ;
2008-02-05 07:49:56 +03:00
/* Host must always specify the capacity. */
2008-05-03 06:50:49 +04:00
vdev - > config - > get ( vdev , offsetof ( struct virtio_blk_config , capacity ) ,
& cap , sizeof ( cap ) ) ;
2007-10-22 05:03:38 +04:00
/* If capacity is too big, truncate with warning. */
if ( ( sector_t ) cap ! = cap ) {
dev_warn ( & vdev - > dev , " Capacity %llu too large: truncating \n " ,
( unsigned long long ) cap ) ;
cap = ( sector_t ) - 1 ;
}
set_capacity ( vblk - > disk , cap ) ;
2008-02-05 07:49:56 +03:00
/* Host can optionally specify maximum segment size and number of
* segments . */
err = virtio_config_val ( vdev , VIRTIO_BLK_F_SIZE_MAX ,
offsetof ( struct virtio_blk_config , size_max ) ,
& v ) ;
2007-10-22 05:03:38 +04:00
if ( ! err )
blk_queue_max_segment_size ( vblk - > disk - > queue , v ) ;
2008-02-05 07:49:56 +03:00
err = virtio_config_val ( vdev , VIRTIO_BLK_F_SEG_MAX ,
offsetof ( struct virtio_blk_config , seg_max ) ,
& v ) ;
2007-10-22 05:03:38 +04:00
if ( ! err )
blk_queue_max_hw_segments ( vblk - > disk - > queue , v ) ;
add_disk ( vblk - > disk ) ;
return 0 ;
out_put_disk :
put_disk ( vblk - > disk ) ;
out_mempool :
mempool_destroy ( vblk - > pool ) ;
out_free_vq :
vdev - > config - > del_vq ( vblk - > vq ) ;
out_free_vblk :
kfree ( vblk ) ;
out :
return err ;
}
static void virtblk_remove ( struct virtio_device * vdev )
{
struct virtio_blk * vblk = vdev - > priv ;
2008-02-05 07:50:03 +03:00
/* Nothing should be pending. */
2007-10-22 05:03:38 +04:00
BUG_ON ( ! list_empty ( & vblk - > reqs ) ) ;
2008-02-05 07:50:03 +03:00
/* Stop all the virtqueues. */
vdev - > config - > reset ( vdev ) ;
2007-10-22 05:03:38 +04:00
blk_cleanup_queue ( vblk - > disk - > queue ) ;
put_disk ( vblk - > disk ) ;
mempool_destroy ( vblk - > pool ) ;
2007-11-19 19:20:42 +03:00
vdev - > config - > del_vq ( vblk - > vq ) ;
2007-10-22 05:03:38 +04:00
kfree ( vblk ) ;
}
static struct virtio_device_id id_table [ ] = {
{ VIRTIO_ID_BLOCK , VIRTIO_DEV_ANY_ID } ,
{ 0 } ,
} ;
2008-05-03 06:50:50 +04:00
static unsigned int features [ ] = {
VIRTIO_BLK_F_BARRIER , VIRTIO_BLK_F_SEG_MAX , VIRTIO_BLK_F_SIZE_MAX ,
} ;
2007-10-22 05:03:38 +04:00
static struct virtio_driver virtio_blk = {
2008-05-03 06:50:50 +04:00
. feature_table = features ,
. feature_table_size = ARRAY_SIZE ( features ) ,
2007-10-22 05:03:38 +04:00
. driver . name = KBUILD_MODNAME ,
. driver . owner = THIS_MODULE ,
. id_table = id_table ,
. probe = virtblk_probe ,
. remove = __devexit_p ( virtblk_remove ) ,
} ;
static int __init init ( void )
{
2008-01-31 17:53:53 +03:00
major = register_blkdev ( 0 , " virtblk " ) ;
if ( major < 0 )
return major ;
2007-10-22 05:03:38 +04:00
return register_virtio_driver ( & virtio_blk ) ;
}
static void __exit fini ( void )
{
2008-01-31 17:53:53 +03:00
unregister_blkdev ( major , " virtblk " ) ;
2007-10-22 05:03:38 +04:00
unregister_virtio_driver ( & virtio_blk ) ;
}
module_init ( init ) ;
module_exit ( fini ) ;
MODULE_DEVICE_TABLE ( virtio , id_table ) ;
MODULE_DESCRIPTION ( " Virtio block driver " ) ;
MODULE_LICENSE ( " GPL " ) ;