/* * Randomness driver for virtio * Copyright (C) 2007, 2008 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 as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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 the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */#include <linux/err.h>#include <linux/hw_random.h>#include <linux/scatterlist.h>#include <linux/spinlock.h>#include <linux/virtio.h>#include <linux/virtio_rng.h>#include <linux/module.h>staticDEFINE_IDA(rng_index_ida);structvirtrng_info{structhwrnghwrng;structvirtqueue*vq;structcompletionhave_data;charname[25];unsignedintdata_avail;intindex;boolbusy;boolhwrng_register_done;boolhwrng_removed;};staticvoidrandom_recv_done(structvirtqueue*vq){structvirtrng_info*vi=vq->vdev->priv;/* We can get spurious callbacks, e.g. shared IRQs + virtio_pci. */if(!virtqueue_get_buf(vi->vq,&vi->data_avail))return;complete(&vi->have_data);}/* The host will fill any buffer we give it with sweet, sweet randomness. */staticvoidregister_buffer(structvirtrng_info*vi,u8*buf,size_tsize){structscatterlistsg;sg_init_one(&sg,buf,size);/* There should always be room for one buffer. */virtqueue_add_inbuf(vi->vq,&sg,1,buf,GFP_KERNEL);virtqueue_kick(vi->vq);}staticintvirtio_read(structhwrng*rng,void*buf,size_tsize,boolwait){intret;structvirtrng_info*vi=(structvirtrng_info*)rng->priv;if(vi->hwrng_removed)return-ENODEV;if(!vi->busy){vi->busy=true;init_completion(&vi->have_data);register_buffer(vi,buf,size);}if(!wait)return0;ret=wait_for_completion_killable(&vi->have_data);if(ret<0)returnret;vi->busy=false;returnvi->data_avail;}staticvoidvirtio_cleanup(structhwrng*rng){structvirtrng_info*vi=(structvirtrng_info*)rng->priv;if(vi->busy)wait_for_completion(&vi->have_data);}staticintprobe_common(structvirtio_device*vdev){interr,index;structvirtrng_info*vi=NULL;vi=kzalloc(sizeof(structvirtrng_info),GFP_KERNEL);if(!vi)return-ENOMEM;vi->index=index=ida_simple_get(&rng_index_ida,0,0,GFP_KERNEL);if(index<0){err=index;gotoerr_ida;}sprintf(vi->name,"virtio_rng.%d",index);init_completion(&vi->have_data);vi->hwrng=(structhwrng){.read=virtio_read,.cleanup=virtio_cleanup,.priv=(unsignedlong)vi,.name=vi->name,.quality=1000,};vdev->priv=vi;/* We expect a single virtqueue. */vi->vq=virtio_find_single_vq(vdev,random_recv_done,"input");if(IS_ERR(vi->vq)){err=PTR_ERR(vi->vq);gotoerr_find;}return0;err_find:ida_simple_remove(&rng_index_ida,index);err_ida:kfree(vi);returnerr;}staticvoidremove_common(structvirtio_device*vdev){structvirtrng_info*vi=vdev->priv;vi->hwrng_removed=true;vi->data_avail=0;complete(&vi->have_data);vdev->config->reset(vdev);vi->busy=false;if(vi->hwrng_register_done)hwrng_unregister(&vi->hwrng);vdev->config->del_vqs(vdev);ida_simple_remove(&rng_index_ida,vi->index);kfree(vi);}staticintvirtrng_probe(structvirtio_device*vdev){returnprobe_common(vdev);}staticvoidvirtrng_remove(structvirtio_device*vdev){remove_common(vdev);}staticvoidvirtrng_scan(structvirtio_device*vdev){structvirtrng_info*vi=vdev->priv;interr;err=hwrng_register(&vi->hwrng);if(!err)vi->hwrng_register_done=true;}#ifdef CONFIG_PM_SLEEPstaticintvirtrng_freeze(structvirtio_device*vdev){remove_common(vdev);return0;}staticintvirtrng_restore(structvirtio_device*vdev){returnprobe_common(vdev);}#endifstaticstructvirtio_device_idid_table[]={{VIRTIO_ID_RNG,VIRTIO_DEV_ANY_ID},{0},};staticstructvirtio_drivervirtio_rng_driver={.driver.name=KBUILD_MODNAME,.driver.owner=THIS_MODULE,.id_table=id_table,.probe=virtrng_probe,.remove=virtrng_remove,.scan=virtrng_scan,#ifdef CONFIG_PM_SLEEP.freeze=virtrng_freeze,.restore=virtrng_restore,#endif};module_virtio_driver(virtio_rng_driver);MODULE_DEVICE_TABLE(virtio,id_table);MODULE_DESCRIPTION("Virtio random number driver");MODULE_LICENSE("GPL");