2018-07-21 16:31:39 +03:00
// SPDX-License-Identifier: GPL-2.0
2016-12-03 23:34:40 +03:00
/*
* uio_hv_generic - generic UIO driver for VMBus
*
* Copyright ( c ) 2013 - 2016 Brocade Communications Systems , Inc .
* Copyright ( c ) 2016 , Microsoft Corporation .
*
* Since the driver does not declare any device ids , you must allocate
* id and bind the device to the driver yourself . For example :
*
2018-01-05 01:13:27 +03:00
* Associate Network GUID with UIO device
2016-12-03 23:34:40 +03:00
* # echo " f8615163-df3e-46c5-913f-f2d2f965ed0e " \
2018-01-05 01:13:27 +03:00
* > / sys / bus / vmbus / drivers / uio_hv_generic / new_id
* Then rebind
* # echo - n " ed963694-e847-4b2a-85af-bc9cfc11d6f3 " \
2016-12-03 23:34:40 +03:00
* > / sys / bus / vmbus / drivers / hv_netvsc / unbind
2018-01-05 01:13:27 +03:00
* # echo - n " ed963694-e847-4b2a-85af-bc9cfc11d6f3 " \
2016-12-03 23:34:40 +03:00
* > / sys / bus / vmbus / drivers / uio_hv_generic / bind
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/device.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/uio_driver.h>
# include <linux/netdevice.h>
# include <linux/if_ether.h>
# include <linux/skbuff.h>
# include <linux/hyperv.h>
# include <linux/vmalloc.h>
# include <linux/slab.h>
# include "../hv/hyperv_vmbus.h"
2018-08-11 02:06:09 +03:00
# define DRIVER_VERSION "0.02.1"
2016-12-03 23:34:40 +03:00
# define DRIVER_AUTHOR "Stephen Hemminger <sthemmin at microsoft.com>"
# define DRIVER_DESC "Generic UIO driver for VMBus devices"
2018-01-09 23:57:30 +03:00
# define HV_RING_SIZE 512 /* pages */
2018-08-11 02:06:09 +03:00
# define SEND_BUFFER_SIZE (16 * 1024 * 1024)
# define RECV_BUFFER_SIZE (31 * 1024 * 1024)
2018-01-09 23:57:30 +03:00
2016-12-03 23:34:40 +03:00
/*
* List of resources to be mapped to user space
* can be extended up to MAX_UIO_MAPS ( 5 ) items
*/
enum hv_uio_map {
TXRX_RING_MAP = 0 ,
INT_PAGE_MAP ,
MON_PAGE_MAP ,
2018-01-09 23:57:30 +03:00
RECV_BUF_MAP ,
SEND_BUF_MAP
2016-12-03 23:34:40 +03:00
} ;
struct hv_uio_private_data {
struct uio_info info ;
struct hv_device * device ;
2018-09-14 19:10:20 +03:00
atomic_t refcnt ;
2018-01-09 23:57:30 +03:00
void * recv_buf ;
2021-10-25 15:21:09 +03:00
struct vmbus_gpadl recv_gpadl ;
2018-01-09 23:57:30 +03:00
char recv_name [ 32 ] ; /* "recv_4294967295" */
void * send_buf ;
2021-10-25 15:21:09 +03:00
struct vmbus_gpadl send_gpadl ;
2018-01-09 23:57:30 +03:00
char send_name [ 32 ] ;
2016-12-03 23:34:40 +03:00
} ;
/*
* This is the irqcontrol callback to be registered to uio_info .
* It can be used to disable / enable interrupt from user space processes .
*
* @ param info
* pointer to uio_info .
* @ param irq_state
* state value . 1 to enable interrupt , 0 to disable interrupt .
*/
static int
hv_uio_irqcontrol ( struct uio_info * info , s32 irq_state )
{
struct hv_uio_private_data * pdata = info - > priv ;
struct hv_device * dev = pdata - > device ;
dev - > channel - > inbound . ring_buffer - > interrupt_mask = ! irq_state ;
virt_mb ( ) ;
return 0 ;
}
/*
* Callback from vmbus_event when something is in inbound ring .
*/
static void hv_uio_channel_cb ( void * context )
{
2018-04-16 21:19:26 +03:00
struct vmbus_channel * chan = context ;
struct hv_device * hv_dev = chan - > device_obj ;
struct hv_uio_private_data * pdata = hv_get_drvdata ( hv_dev ) ;
2016-12-03 23:34:40 +03:00
2018-04-16 21:19:26 +03:00
chan - > inbound . ring_buffer - > interrupt_mask = 1 ;
2016-12-03 23:34:40 +03:00
virt_mb ( ) ;
uio_event_notify ( & pdata - > info ) ;
}
2018-01-09 23:57:32 +03:00
/*
* Callback from vmbus_event when channel is rescinded .
*/
static void hv_uio_rescind ( struct vmbus_channel * channel )
{
struct hv_device * hv_dev = channel - > primary_channel - > device_obj ;
struct hv_uio_private_data * pdata = hv_get_drvdata ( hv_dev ) ;
/*
* Turn off the interrupt file handle
* Next read for event will return - EIO
*/
pdata - > info . irq = 0 ;
/* Wake up reader */
uio_event_notify ( & pdata - > info ) ;
}
2018-01-09 23:57:30 +03:00
2018-04-16 21:19:27 +03:00
/* Sysfs API to allow mmap of the ring buffers
* The ring buffer is allocated as contiguous memory by vmbus_open
2018-02-05 21:40:27 +03:00
*/
static int hv_uio_ring_mmap ( struct file * filp , struct kobject * kobj ,
struct bin_attribute * attr ,
struct vm_area_struct * vma )
{
struct vmbus_channel * channel
= container_of ( kobj , struct vmbus_channel , kobj ) ;
2018-09-14 19:10:16 +03:00
void * ring_buffer = page_address ( channel - > ringbuffer_page ) ;
2018-02-05 21:40:27 +03:00
2018-09-14 19:10:20 +03:00
if ( channel - > state ! = CHANNEL_OPENED_STATE )
return - ENODEV ;
2018-02-05 21:40:27 +03:00
2018-09-14 19:10:16 +03:00
return vm_iomap_memory ( vma , virt_to_phys ( ring_buffer ) ,
2018-04-16 21:19:27 +03:00
channel - > ringbuffer_pagecount < < PAGE_SHIFT ) ;
2018-02-05 21:40:27 +03:00
}
2018-04-16 21:19:24 +03:00
static const struct bin_attribute ring_buffer_bin_attr = {
2018-02-05 21:40:27 +03:00
. attr = {
. name = " ring " ,
. mode = 0600 ,
} ,
2018-04-16 21:19:24 +03:00
. size = 2 * HV_RING_SIZE * PAGE_SIZE ,
2018-02-05 21:40:27 +03:00
. mmap = hv_uio_ring_mmap ,
} ;
2018-04-16 21:19:26 +03:00
/* Callback from VMBUS subsystem when new channel created. */
2018-02-05 21:40:27 +03:00
static void
hv_uio_new_channel ( struct vmbus_channel * new_sc )
{
struct hv_device * hv_dev = new_sc - > primary_channel - > device_obj ;
struct device * device = & hv_dev - > device ;
const size_t ring_bytes = HV_RING_SIZE * PAGE_SIZE ;
int ret ;
/* Create host communication ring */
ret = vmbus_open ( new_sc , ring_bytes , ring_bytes , NULL , 0 ,
2018-04-16 21:19:26 +03:00
hv_uio_channel_cb , new_sc ) ;
2018-02-05 21:40:27 +03:00
if ( ret ) {
dev_err ( device , " vmbus_open subchannel failed: %d \n " , ret ) ;
return ;
}
/* Disable interrupts on sub channel */
new_sc - > inbound . ring_buffer - > interrupt_mask = 1 ;
set_channel_read_mode ( new_sc , HV_CALL_ISR ) ;
ret = sysfs_create_bin_file ( & new_sc - > kobj , & ring_buffer_bin_attr ) ;
if ( ret ) {
dev_err ( device , " sysfs create ring bin file failed; %d \n " , ret ) ;
vmbus_close ( new_sc ) ;
}
}
2018-09-14 19:10:20 +03:00
/* free the reserved buffers for send and receive */
2018-01-09 23:57:30 +03:00
static void
hv_uio_cleanup ( struct hv_device * dev , struct hv_uio_private_data * pdata )
{
2021-10-25 15:21:09 +03:00
if ( pdata - > send_gpadl . gpadl_handle ) {
vmbus_teardown_gpadl ( dev - > channel , & pdata - > send_gpadl ) ;
2018-09-14 19:10:20 +03:00
vfree ( pdata - > send_buf ) ;
}
2018-01-09 23:57:30 +03:00
2021-10-25 15:21:09 +03:00
if ( pdata - > recv_gpadl . gpadl_handle ) {
vmbus_teardown_gpadl ( dev - > channel , & pdata - > recv_gpadl ) ;
2018-09-14 19:10:20 +03:00
vfree ( pdata - > recv_buf ) ;
}
}
/* VMBus primary channel is opened on first use */
static int
hv_uio_open ( struct uio_info * info , struct inode * inode )
{
struct hv_uio_private_data * pdata
= container_of ( info , struct hv_uio_private_data , info ) ;
struct hv_device * dev = pdata - > device ;
int ret ;
if ( atomic_inc_return ( & pdata - > refcnt ) ! = 1 )
return 0 ;
2018-12-10 21:18:19 +03:00
vmbus_set_chn_rescind_callback ( dev - > channel , hv_uio_rescind ) ;
vmbus_set_sc_create_callback ( dev - > channel , hv_uio_new_channel ) ;
2018-09-14 19:10:20 +03:00
ret = vmbus_connect_ring ( dev - > channel ,
hv_uio_channel_cb , dev - > channel ) ;
if ( ret = = 0 )
dev - > channel - > inbound . ring_buffer - > interrupt_mask = 1 ;
else
atomic_dec ( & pdata - > refcnt ) ;
return ret ;
}
/* VMBus primary channel is closed on last close */
static int
hv_uio_release ( struct uio_info * info , struct inode * inode )
{
struct hv_uio_private_data * pdata
= container_of ( info , struct hv_uio_private_data , info ) ;
struct hv_device * dev = pdata - > device ;
int ret = 0 ;
if ( atomic_dec_and_test ( & pdata - > refcnt ) )
ret = vmbus_disconnect_ring ( dev - > channel ) ;
return ret ;
2018-01-09 23:57:30 +03:00
}
2016-12-03 23:34:40 +03:00
static int
hv_uio_probe ( struct hv_device * dev ,
const struct hv_vmbus_device_id * dev_id )
{
2018-09-14 19:10:20 +03:00
struct vmbus_channel * channel = dev - > channel ;
2016-12-03 23:34:40 +03:00
struct hv_uio_private_data * pdata ;
2018-09-14 19:10:20 +03:00
void * ring_buffer ;
2016-12-03 23:34:40 +03:00
int ret ;
2018-09-14 19:10:20 +03:00
/* Communicating with host has to be via shared memory not hypercall */
if ( ! channel - > offermsg . monitor_allocated ) {
dev_err ( & dev - > device , " vmbus channel requires hypercall \n " ) ;
return - ENOTSUPP ;
}
2020-11-19 18:49:03 +03:00
pdata = devm_kzalloc ( & dev - > device , sizeof ( * pdata ) , GFP_KERNEL ) ;
2016-12-03 23:34:40 +03:00
if ( ! pdata )
return - ENOMEM ;
2018-09-14 19:10:20 +03:00
ret = vmbus_alloc_ring ( channel , HV_RING_SIZE * PAGE_SIZE ,
HV_RING_SIZE * PAGE_SIZE ) ;
2016-12-03 23:34:40 +03:00
if ( ret )
2020-11-19 18:49:03 +03:00
return ret ;
2016-12-03 23:34:40 +03:00
2018-09-14 19:10:20 +03:00
set_channel_read_mode ( channel , HV_CALL_ISR ) ;
2016-12-03 23:34:40 +03:00
/* Fill general uio info */
pdata - > info . name = " uio_hv_generic " ;
pdata - > info . version = DRIVER_VERSION ;
pdata - > info . irqcontrol = hv_uio_irqcontrol ;
2018-09-14 19:10:20 +03:00
pdata - > info . open = hv_uio_open ;
pdata - > info . release = hv_uio_release ;
2016-12-03 23:34:40 +03:00
pdata - > info . irq = UIO_IRQ_CUSTOM ;
2018-09-14 19:10:20 +03:00
atomic_set ( & pdata - > refcnt , 0 ) ;
2016-12-03 23:34:40 +03:00
/* mem resources */
pdata - > info . mem [ TXRX_RING_MAP ] . name = " txrx_rings " ;
2018-09-14 19:10:20 +03:00
ring_buffer = page_address ( channel - > ringbuffer_page ) ;
2016-12-03 23:34:40 +03:00
pdata - > info . mem [ TXRX_RING_MAP ] . addr
2018-09-14 19:10:20 +03:00
= ( uintptr_t ) virt_to_phys ( ring_buffer ) ;
2016-12-03 23:34:40 +03:00
pdata - > info . mem [ TXRX_RING_MAP ] . size
2018-09-14 19:10:20 +03:00
= channel - > ringbuffer_pagecount < < PAGE_SHIFT ;
2018-09-14 19:10:19 +03:00
pdata - > info . mem [ TXRX_RING_MAP ] . memtype = UIO_MEM_IOVA ;
2016-12-03 23:34:40 +03:00
pdata - > info . mem [ INT_PAGE_MAP ] . name = " int_page " ;
2018-01-05 01:13:28 +03:00
pdata - > info . mem [ INT_PAGE_MAP ] . addr
2018-01-10 19:42:38 +03:00
= ( uintptr_t ) vmbus_connection . int_page ;
2016-12-03 23:34:40 +03:00
pdata - > info . mem [ INT_PAGE_MAP ] . size = PAGE_SIZE ;
pdata - > info . mem [ INT_PAGE_MAP ] . memtype = UIO_MEM_LOGICAL ;
2018-01-05 01:13:28 +03:00
pdata - > info . mem [ MON_PAGE_MAP ] . name = " monitor_page " ;
pdata - > info . mem [ MON_PAGE_MAP ] . addr
2018-01-10 19:42:38 +03:00
= ( uintptr_t ) vmbus_connection . monitor_pages [ 1 ] ;
2016-12-03 23:34:40 +03:00
pdata - > info . mem [ MON_PAGE_MAP ] . size = PAGE_SIZE ;
pdata - > info . mem [ MON_PAGE_MAP ] . memtype = UIO_MEM_LOGICAL ;
2018-01-09 23:57:30 +03:00
pdata - > recv_buf = vzalloc ( RECV_BUFFER_SIZE ) ;
if ( pdata - > recv_buf = = NULL ) {
ret = - ENOMEM ;
2021-05-09 10:13:12 +03:00
goto fail_free_ring ;
2018-01-09 23:57:30 +03:00
}
2018-09-14 19:10:20 +03:00
ret = vmbus_establish_gpadl ( channel , pdata - > recv_buf ,
2018-01-09 23:57:30 +03:00
RECV_BUFFER_SIZE , & pdata - > recv_gpadl ) ;
2021-05-09 10:13:03 +03:00
if ( ret ) {
vfree ( pdata - > recv_buf ) ;
2018-01-09 23:57:30 +03:00
goto fail_close ;
2021-05-09 10:13:03 +03:00
}
2018-01-09 23:57:30 +03:00
/* put Global Physical Address Label in name */
snprintf ( pdata - > recv_name , sizeof ( pdata - > recv_name ) ,
2021-10-25 15:21:09 +03:00
" recv:%u " , pdata - > recv_gpadl . gpadl_handle ) ;
2018-01-09 23:57:30 +03:00
pdata - > info . mem [ RECV_BUF_MAP ] . name = pdata - > recv_name ;
pdata - > info . mem [ RECV_BUF_MAP ] . addr
2018-01-12 18:51:14 +03:00
= ( uintptr_t ) pdata - > recv_buf ;
2018-01-09 23:57:30 +03:00
pdata - > info . mem [ RECV_BUF_MAP ] . size = RECV_BUFFER_SIZE ;
pdata - > info . mem [ RECV_BUF_MAP ] . memtype = UIO_MEM_VIRTUAL ;
pdata - > send_buf = vzalloc ( SEND_BUFFER_SIZE ) ;
if ( pdata - > send_buf = = NULL ) {
ret = - ENOMEM ;
goto fail_close ;
}
2018-09-14 19:10:20 +03:00
ret = vmbus_establish_gpadl ( channel , pdata - > send_buf ,
2018-01-09 23:57:30 +03:00
SEND_BUFFER_SIZE , & pdata - > send_gpadl ) ;
2021-05-09 10:13:03 +03:00
if ( ret ) {
vfree ( pdata - > send_buf ) ;
2018-01-09 23:57:30 +03:00
goto fail_close ;
2021-05-09 10:13:03 +03:00
}
2018-01-09 23:57:30 +03:00
snprintf ( pdata - > send_name , sizeof ( pdata - > send_name ) ,
2021-10-25 15:21:09 +03:00
" send:%u " , pdata - > send_gpadl . gpadl_handle ) ;
2018-01-09 23:57:30 +03:00
pdata - > info . mem [ SEND_BUF_MAP ] . name = pdata - > send_name ;
pdata - > info . mem [ SEND_BUF_MAP ] . addr
2018-01-12 18:51:14 +03:00
= ( uintptr_t ) pdata - > send_buf ;
2018-01-09 23:57:30 +03:00
pdata - > info . mem [ SEND_BUF_MAP ] . size = SEND_BUFFER_SIZE ;
pdata - > info . mem [ SEND_BUF_MAP ] . memtype = UIO_MEM_VIRTUAL ;
2016-12-03 23:34:40 +03:00
pdata - > info . priv = pdata ;
pdata - > device = dev ;
ret = uio_register_device ( & dev - > device , & pdata - > info ) ;
if ( ret ) {
dev_err ( & dev - > device , " hv_uio register failed \n " ) ;
goto fail_close ;
}
2018-09-14 19:10:20 +03:00
ret = sysfs_create_bin_file ( & channel - > kobj , & ring_buffer_bin_attr ) ;
2018-04-16 21:19:25 +03:00
if ( ret )
dev_notice ( & dev - > device ,
" sysfs create ring bin file failed; %d \n " , ret ) ;
2016-12-03 23:34:40 +03:00
hv_set_drvdata ( dev , pdata ) ;
return 0 ;
fail_close :
2018-01-09 23:57:30 +03:00
hv_uio_cleanup ( dev , pdata ) ;
2021-05-09 10:13:12 +03:00
fail_free_ring :
vmbus_free_ring ( dev - > channel ) ;
2016-12-03 23:34:40 +03:00
return ret ;
}
static int
hv_uio_remove ( struct hv_device * dev )
{
struct hv_uio_private_data * pdata = hv_get_drvdata ( dev ) ;
if ( ! pdata )
return 0 ;
2020-05-07 18:13:43 +03:00
sysfs_remove_bin_file ( & dev - > channel - > kobj , & ring_buffer_bin_attr ) ;
2016-12-03 23:34:40 +03:00
uio_unregister_device ( & pdata - > info ) ;
2018-01-09 23:57:30 +03:00
hv_uio_cleanup ( dev , pdata ) ;
2018-09-14 19:10:20 +03:00
vmbus_free_ring ( dev - > channel ) ;
2016-12-03 23:34:40 +03:00
return 0 ;
}
static struct hv_driver hv_uio_drv = {
. name = " uio_hv_generic " ,
. id_table = NULL , /* only dynamic id's */
. probe = hv_uio_probe ,
. remove = hv_uio_remove ,
} ;
static int __init
hyperv_module_init ( void )
{
return vmbus_driver_register ( & hv_uio_drv ) ;
}
static void __exit
hyperv_module_exit ( void )
{
vmbus_driver_unregister ( & hv_uio_drv ) ;
}
module_init ( hyperv_module_init ) ;
module_exit ( hyperv_module_exit ) ;
MODULE_VERSION ( DRIVER_VERSION ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( DRIVER_AUTHOR ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;