mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 04:34:03 +02:00
hv_netvsc: Allocate send/receive buffers using vmbus_alloc_buffer()
On CoCo VMs without confidential VMBus, the netvsc send and receive buffers must be made host-visible by decrypting them. These buffers are vmalloc'ed, but set_memory_decrypted()/encrypted() do not work on vmalloc'ed memory. This use case is (so far) unique to netvsc, so solve it locally rather than changing the set_memory() or allocation APIs. Use vmbus_alloc_buffer() to allocate the send and receive buffers, which will make them host-visible. Store the list of memory chunks in the netvsc_device struct so they can be individually freed later. Use vmbus_establish_gpadl_caller_decrypted() so there is no attempt to decrypt the virtual address. Appropriately free the buffers with vmbus_free_buffer(). Because vunmap() and set_memory_encrypted() must run in process context, replace the rcu_head/call_rcu() pair used to defer free_netvsc_device() with rcu_work/queue_rcu_work(). This also fixes a small race condition where the buffers may be accessed while being re-encrypted by moving the re-encryption after the RCU grace period. Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com> Reviewed-by: Michael Kelley <mhklinux@outlook.com> Signed-off-by: Wei Liu <wei.liu@kernel.org>
This commit is contained in:
parent
73fe42af95
commit
f85e1cc5ec
|
|
@ -220,6 +220,8 @@ struct net_device_context;
|
|||
|
||||
extern u32 netvsc_ring_bytes;
|
||||
|
||||
int netvsc_workqueue_init(void);
|
||||
void netvsc_workqueue_destroy(void);
|
||||
struct netvsc_device *netvsc_device_add(struct hv_device *device,
|
||||
const struct netvsc_device_info *info);
|
||||
int netvsc_alloc_recv_comp_ring(struct netvsc_device *net_device, u32 q_idx);
|
||||
|
|
@ -1158,6 +1160,8 @@ struct netvsc_device {
|
|||
/* Receive buffer allocated by us but manages by NetVSP */
|
||||
void *recv_buf;
|
||||
u32 recv_buf_size; /* allocated bytes */
|
||||
struct page **recv_buf_chunks;
|
||||
u32 recv_buf_chunk_cnt;
|
||||
struct vmbus_gpadl recv_buf_gpadl_handle;
|
||||
u32 recv_section_cnt;
|
||||
u32 recv_section_size;
|
||||
|
|
@ -1166,6 +1170,8 @@ struct netvsc_device {
|
|||
/* Send buffer allocated by us */
|
||||
void *send_buf;
|
||||
u32 send_buf_size;
|
||||
struct page **send_buf_chunks;
|
||||
u32 send_buf_chunk_cnt;
|
||||
struct vmbus_gpadl send_buf_gpadl_handle;
|
||||
u32 send_section_cnt;
|
||||
u32 send_section_size;
|
||||
|
|
@ -1193,7 +1199,7 @@ struct netvsc_device {
|
|||
|
||||
struct netvsc_channel chan_table[VRSS_CHANNEL_MAX];
|
||||
|
||||
struct rcu_head rcu;
|
||||
struct rcu_work rwork;
|
||||
};
|
||||
|
||||
/* NdisInitialize message */
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@
|
|||
#include "hyperv_net.h"
|
||||
#include "netvsc_trace.h"
|
||||
|
||||
static struct workqueue_struct *netvsc_wq;
|
||||
|
||||
/*
|
||||
* Switch the data path from the synthetic interface to the VF
|
||||
* interface.
|
||||
|
|
@ -126,6 +128,47 @@ static void netvsc_subchan_work(struct work_struct *w)
|
|||
rtnl_unlock();
|
||||
}
|
||||
|
||||
static void __free_netvsc_device(struct netvsc_device *nvdev)
|
||||
{
|
||||
int i;
|
||||
|
||||
kfree(nvdev->extension);
|
||||
|
||||
vmbus_free_buffer(nvdev->recv_buf, nvdev->recv_buf_chunks,
|
||||
nvdev->recv_buf_chunk_cnt);
|
||||
vmbus_free_buffer(nvdev->send_buf, nvdev->send_buf_chunks,
|
||||
nvdev->send_buf_chunk_cnt);
|
||||
bitmap_free(nvdev->send_section_map);
|
||||
|
||||
for (i = 0; i < VRSS_CHANNEL_MAX; i++) {
|
||||
xdp_rxq_info_unreg(&nvdev->chan_table[i].xdp_rxq);
|
||||
kfree(nvdev->chan_table[i].recv_buf);
|
||||
vfree(nvdev->chan_table[i].mrc.slots);
|
||||
}
|
||||
|
||||
kfree(nvdev);
|
||||
}
|
||||
|
||||
static void free_netvsc_device(struct work_struct *w)
|
||||
{
|
||||
struct rcu_work *rwork = to_rcu_work(w);
|
||||
|
||||
__free_netvsc_device(container_of(rwork, struct netvsc_device, rwork));
|
||||
}
|
||||
|
||||
int netvsc_workqueue_init(void)
|
||||
{
|
||||
netvsc_wq = alloc_workqueue("hv_netvsc", WQ_UNBOUND, 0);
|
||||
|
||||
return netvsc_wq ? 0 : -ENOMEM;
|
||||
}
|
||||
|
||||
void netvsc_workqueue_destroy(void)
|
||||
{
|
||||
rcu_barrier();
|
||||
destroy_workqueue(netvsc_wq);
|
||||
}
|
||||
|
||||
static struct netvsc_device *alloc_net_device(void)
|
||||
{
|
||||
struct netvsc_device *net_device;
|
||||
|
|
@ -144,36 +187,18 @@ static struct netvsc_device *alloc_net_device(void)
|
|||
init_completion(&net_device->channel_init_wait);
|
||||
init_waitqueue_head(&net_device->subchan_open);
|
||||
INIT_WORK(&net_device->subchan_work, netvsc_subchan_work);
|
||||
INIT_RCU_WORK(&net_device->rwork, free_netvsc_device);
|
||||
|
||||
return net_device;
|
||||
}
|
||||
|
||||
static void free_netvsc_device(struct rcu_head *head)
|
||||
{
|
||||
struct netvsc_device *nvdev
|
||||
= container_of(head, struct netvsc_device, rcu);
|
||||
int i;
|
||||
|
||||
kfree(nvdev->extension);
|
||||
|
||||
if (!nvdev->recv_buf_gpadl_handle.decrypted)
|
||||
vfree(nvdev->recv_buf);
|
||||
if (!nvdev->send_buf_gpadl_handle.decrypted)
|
||||
vfree(nvdev->send_buf);
|
||||
bitmap_free(nvdev->send_section_map);
|
||||
|
||||
for (i = 0; i < VRSS_CHANNEL_MAX; i++) {
|
||||
xdp_rxq_info_unreg(&nvdev->chan_table[i].xdp_rxq);
|
||||
kfree(nvdev->chan_table[i].recv_buf);
|
||||
vfree(nvdev->chan_table[i].mrc.slots);
|
||||
}
|
||||
|
||||
kfree(nvdev);
|
||||
}
|
||||
|
||||
static void free_netvsc_device_rcu(struct netvsc_device *nvdev)
|
||||
{
|
||||
call_rcu(&nvdev->rcu, free_netvsc_device);
|
||||
/*
|
||||
* Defer the actual free to process context: vunmap() and
|
||||
* set_memory_encrypted() cannot run from RCU softirq context.
|
||||
*/
|
||||
queue_rcu_work(netvsc_wq, &nvdev->rwork);
|
||||
}
|
||||
|
||||
static void netvsc_revoke_recv_buf(struct hv_device *device,
|
||||
|
|
@ -352,7 +377,10 @@ static int netvsc_init_buf(struct hv_device *device,
|
|||
buf_size = min_t(unsigned int, buf_size,
|
||||
NETVSC_RECEIVE_BUFFER_SIZE_LEGACY);
|
||||
|
||||
net_device->recv_buf = vzalloc(buf_size);
|
||||
net_device->recv_buf =
|
||||
vmbus_alloc_buffer(device->channel, buf_size,
|
||||
&net_device->recv_buf_chunks,
|
||||
&net_device->recv_buf_chunk_cnt);
|
||||
if (!net_device->recv_buf) {
|
||||
netdev_err(ndev,
|
||||
"unable to allocate receive buffer of size %u\n",
|
||||
|
|
@ -368,9 +396,10 @@ static int netvsc_init_buf(struct hv_device *device,
|
|||
* channel. Note: This call uses the vmbus connection rather
|
||||
* than the channel to establish the gpadl handle.
|
||||
*/
|
||||
ret = vmbus_establish_gpadl(device->channel, net_device->recv_buf,
|
||||
buf_size,
|
||||
&net_device->recv_buf_gpadl_handle);
|
||||
ret = vmbus_establish_gpadl_caller_decrypted(device->channel,
|
||||
net_device->recv_buf,
|
||||
buf_size,
|
||||
&net_device->recv_buf_gpadl_handle);
|
||||
if (ret != 0) {
|
||||
netdev_err(ndev,
|
||||
"unable to establish receive buffer's gpadl\n");
|
||||
|
|
@ -458,7 +487,10 @@ static int netvsc_init_buf(struct hv_device *device,
|
|||
buf_size = device_info->send_sections * device_info->send_section_size;
|
||||
buf_size = round_up(buf_size, PAGE_SIZE);
|
||||
|
||||
net_device->send_buf = vzalloc(buf_size);
|
||||
net_device->send_buf =
|
||||
vmbus_alloc_buffer(device->channel, buf_size,
|
||||
&net_device->send_buf_chunks,
|
||||
&net_device->send_buf_chunk_cnt);
|
||||
if (!net_device->send_buf) {
|
||||
netdev_err(ndev, "unable to allocate send buffer of size %u\n",
|
||||
buf_size);
|
||||
|
|
@ -471,9 +503,10 @@ static int netvsc_init_buf(struct hv_device *device,
|
|||
* channel. Note: This call uses the vmbus connection rather
|
||||
* than the channel to establish the gpadl handle.
|
||||
*/
|
||||
ret = vmbus_establish_gpadl(device->channel, net_device->send_buf,
|
||||
buf_size,
|
||||
&net_device->send_buf_gpadl_handle);
|
||||
ret = vmbus_establish_gpadl_caller_decrypted(device->channel,
|
||||
net_device->send_buf,
|
||||
buf_size,
|
||||
&net_device->send_buf_gpadl_handle);
|
||||
if (ret != 0) {
|
||||
netdev_err(ndev,
|
||||
"unable to establish send buffer's gpadl\n");
|
||||
|
|
@ -1874,7 +1907,11 @@ struct netvsc_device *netvsc_device_add(struct hv_device *device,
|
|||
netif_napi_del(&net_device->chan_table[0].napi);
|
||||
|
||||
cleanup2:
|
||||
free_netvsc_device(&net_device->rcu);
|
||||
/*
|
||||
* net_device was never published, so we don't need to wait for an
|
||||
* RCU grace period -- call the free routine synchronously.
|
||||
*/
|
||||
__free_netvsc_device(net_device);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2867,12 +2867,17 @@ static void __exit netvsc_drv_exit(void)
|
|||
{
|
||||
unregister_netdevice_notifier(&netvsc_netdev_notifier);
|
||||
vmbus_driver_unregister(&netvsc_drv);
|
||||
netvsc_workqueue_destroy();
|
||||
}
|
||||
|
||||
static int __init netvsc_drv_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = netvsc_workqueue_init();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (ring_size < RING_SIZE_MIN) {
|
||||
ring_size = RING_SIZE_MIN;
|
||||
pr_info("Increased ring_size to %u (min allowed)\n",
|
||||
|
|
@ -2890,6 +2895,7 @@ static int __init netvsc_drv_init(void)
|
|||
|
||||
err_vmbus_reg:
|
||||
unregister_netdevice_notifier(&netvsc_netdev_notifier);
|
||||
netvsc_workqueue_destroy();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user