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>
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 ;
2008-12-30 18:26:05 +03:00
/* What host tells us, plus 2 for header & tailer. */
unsigned int sg_elems ;
2007-10-22 05:03:38 +04:00
/* Scatterlist: can be too big for stack. */
2008-12-30 18:26:05 +03:00
struct scatterlist sg [ /*sg_elems*/ ] ;
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 ) {
2008-10-01 18:11:20 +04:00
int error ;
2008-05-03 06:50:45 +04:00
switch ( vbr - > status ) {
2007-10-22 05:03:38 +04:00
case VIRTIO_BLK_S_OK :
2008-10-01 18:11:20 +04:00
error = 0 ;
2007-10-22 05:03:38 +04:00
break ;
case VIRTIO_BLK_S_UNSUPP :
2008-10-01 18:11:20 +04:00
error = - ENOTTY ;
2007-10-22 05:03:38 +04:00
break ;
default :
2008-10-01 18:11:20 +04:00
error = - EIO ;
2007-10-22 05:03:38 +04:00
break ;
}
2009-04-23 06:05:19 +04:00
__blk_end_request_all ( vbr - > req , error ) ;
2007-10-22 05:03:38 +04:00
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 ;
2009-05-07 17:24:39 +04:00
vbr - > out_hdr . sector = blk_rq_pos ( vbr - > req ) ;
2008-08-14 11:59:13 +04:00
vbr - > out_hdr . ioprio = req_get_ioprio ( vbr - > req ) ;
2007-10-22 05:03:38 +04:00
} else if ( blk_pc_request ( vbr - > req ) ) {
vbr - > out_hdr . type = VIRTIO_BLK_T_SCSI_CMD ;
vbr - > out_hdr . sector = 0 ;
2008-08-14 11:59:13 +04:00
vbr - > out_hdr . ioprio = req_get_ioprio ( vbr - > req ) ;
2007-10-22 05:03:38 +04:00
} 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 ;
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 ;
2009-05-08 06:54:16 +04:00
while ( ( req = blk_peek_request ( q ) ) ! = NULL ) {
2007-10-22 05:03:38 +04:00
vblk = req - > rq_disk - > private_data ;
2008-12-30 18:26:05 +03:00
BUG_ON ( req - > nr_phys_segments + 2 > vblk - > sg_elems ) ;
2007-10-22 05:03:38 +04:00
/* 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 ;
}
2009-05-08 06:54:16 +04:00
blk_start_request ( req ) ;
2007-10-22 05:03:38 +04:00
issued + + ;
}
if ( issued )
vblk - > vq - > vq_ops - > kick ( vblk - > vq ) ;
}
2008-03-02 18:22:33 +03:00
static int virtblk_ioctl ( struct block_device * bdev , fmode_t mode ,
2007-10-22 05:03:38 +04:00
unsigned cmd , unsigned long data )
{
2008-03-02 18:22:33 +03:00
return scsi_cmd_ioctl ( bdev - > bd_disk - > queue ,
bdev - > bd_disk , mode , cmd ,
2007-10-22 05:03:38 +04:00
( 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 )
{
2008-04-16 22:56:37 +04:00
struct virtio_blk * vblk = bd - > bd_disk - > private_data ;
struct virtio_blk_geometry vgeo ;
int err ;
/* see if the host passed in geometry config */
err = virtio_config_val ( vblk - > vdev , VIRTIO_BLK_F_GEOMETRY ,
offsetof ( struct virtio_blk_config , geometry ) ,
& vgeo ) ;
if ( ! err ) {
geo - > heads = vgeo . heads ;
geo - > sectors = vgeo . sectors ;
geo - > cylinders = vgeo . cylinders ;
} else {
/* some standard values, similar to sd */
geo - > heads = 1 < < 6 ;
geo - > sectors = 1 < < 5 ;
geo - > cylinders = get_capacity ( bd - > bd_disk ) > > 11 ;
}
2008-01-23 19:56:50 +03:00
return 0 ;
}
2007-10-22 05:03:38 +04:00
static struct block_device_operations virtblk_fops = {
2008-03-02 18:22:33 +03:00
. locked_ioctl = virtblk_ioctl ,
2008-01-23 19:56:50 +03:00
. 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-12-30 18:26:05 +03:00
u32 blk_size , sg_elems ;
2007-10-22 05:03:38 +04:00
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 ;
2008-12-30 18:26:05 +03:00
/* We need to know how many segments before we allocate. */
err = virtio_config_val ( vdev , VIRTIO_BLK_F_SEG_MAX ,
offsetof ( struct virtio_blk_config , seg_max ) ,
& sg_elems ) ;
if ( err )
sg_elems = 1 ;
/* We need an extra sg elements at head and tail. */
sg_elems + = 2 ;
vdev - > priv = vblk = kmalloc ( sizeof ( * vblk ) +
sizeof ( vblk - > sg [ 0 ] ) * sg_elems , GFP_KERNEL ) ;
2007-10-22 05:03:38 +04:00
if ( ! vblk ) {
err = - ENOMEM ;
goto out ;
}
INIT_LIST_HEAD ( & vblk - > reqs ) ;
spin_lock_init ( & vblk - > lock ) ;
vblk - > vdev = vdev ;
2008-12-30 18:26:05 +03:00
vblk - > sg_elems = sg_elems ;
sg_init_table ( vblk - > sg , vblk - > sg_elems ) ;
2007-10-22 05:03:38 +04:00
/* 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-10-27 12:45:15 +03:00
queue_flag_set_unlocked ( QUEUE_FLAG_VIRT , vblk - > disk - > queue ) ;
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-05-16 13:17:03 +04:00
/* If disk is read-only in the host, the guest should obey */
if ( virtio_has_feature ( vdev , VIRTIO_BLK_F_RO ) )
set_disk_ro ( vblk - > disk , 1 ) ;
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-12-30 18:26:05 +03:00
/* We can handle whatever the host told us to handle. */
blk_queue_max_phys_segments ( vblk - > disk - > queue , vblk - > sg_elems - 2 ) ;
blk_queue_max_hw_segments ( vblk - > disk - > queue , vblk - > sg_elems - 2 ) ;
2008-12-30 18:26:04 +03:00
/* No real sector limit. */
blk_queue_max_sectors ( vblk - > disk - > queue , - 1U ) ;
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-12-30 18:26:04 +03:00
else
2008-11-27 00:15:50 +03:00
blk_queue_max_segment_size ( vblk - > disk - > queue , - 1U ) ;
2007-10-22 05:03:38 +04:00
2008-05-29 13:08:26 +04:00
/* Host can optionally specify the block size of the device */
err = virtio_config_val ( vdev , VIRTIO_BLK_F_BLK_SIZE ,
offsetof ( struct virtio_blk_config , blk_size ) ,
& blk_size ) ;
if ( ! err )
blk_queue_hardsect_size ( vblk - > disk - > queue , blk_size ) ;
2007-10-22 05:03:38 +04:00
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 ) ;
2008-05-31 00:09:41 +04:00
del_gendisk ( vblk - > disk ) ;
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 ,
2008-05-29 13:08:26 +04:00
VIRTIO_BLK_F_GEOMETRY , VIRTIO_BLK_F_RO , VIRTIO_BLK_F_BLK_SIZE ,
2008-05-03 06:50:50 +04:00
} ;
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 " ) ;