blob: a151d416f39c132a61363b4bdaab8f52e420dd41 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Intel MIC Platform Software Stack (MPSS)
*
* Copyright(c) 2015 Intel Corporation.
*
* Intel SCIF driver.
*/
#include "scif_main.h"
/*
* struct scif_vma_info - Information about a remote memory mapping
* created via scif_mmap(..)
* @vma: VM area struct
* @list: link to list of active vmas
*/
struct scif_vma_info {
struct vm_area_struct *vma;
struct list_head list;
};
void scif_recv_munmap(struct scif_dev *scifdev, struct scifmsg *msg)
{
struct scif_rma_req req;
struct scif_window *window = NULL;
struct scif_window *recv_window =
(struct scif_window *)msg->payload[0];
struct scif_endpt *ep;
ep = (struct scif_endpt *)recv_window->ep;
req.out_window = &window;
req.offset = recv_window->offset;
req.prot = recv_window->prot;
req.nr_bytes = recv_window->nr_pages << PAGE_SHIFT;
req.type = SCIF_WINDOW_FULL;
req.head = &ep->rma_info.reg_list;
msg->payload[0] = ep->remote_ep;
mutex_lock(&ep->rma_info.rma_lock);
/* Does a valid window exist? */
if (scif_query_window(&req)) {
dev_err(&scifdev->sdev->dev,
"%s %d -ENXIO\n", __func__, __LINE__);
msg->uop = SCIF_UNREGISTER_ACK;
goto error;
}
scif_put_window(window, window->nr_pages);
if (!window->ref_count) {
atomic_inc(&ep->rma_info.tw_refcount);
ep->rma_info.async_list_del = 1;
list_del_init(&window->list);
scif_free_window_offset(ep, window, window->offset);
}
error:
mutex_unlock(&ep->rma_info.rma_lock);
if (window && !window->ref_count)
scif_queue_for_cleanup(window, &scif_info.rma);
}
/*
* Remove valid remote memory mappings created via scif_mmap(..) from the
* process address space since the remote node is lost
*/
static void __scif_zap_mmaps(struct scif_endpt *ep)
{
struct list_head *item;
struct scif_vma_info *info;
struct vm_area_struct *vma;
unsigned long size;
spin_lock(&ep->lock);
list_for_each(item, &ep->rma_info.vma_list) {
info = list_entry(item, struct scif_vma_info, list);
vma = info->vma;
size = vma->vm_end - vma->vm_start;
zap_vma_ptes(vma, vma->vm_start, size);
dev_dbg(scif_info.mdev.this_device,
"%s ep %p zap vma %p size 0x%lx\n",
__func__, ep, info->vma, size);
}
spin_unlock(&ep->lock);
}
/*
* Traverse the list of endpoints for a particular remote node and
* zap valid remote memory mappings since the remote node is lost
*/
static void _scif_zap_mmaps(int node, struct list_head *head)
{
struct scif_endpt *ep;
struct list_head *item;
mutex_lock(&scif_info.connlock);
list_for_each(item, head) {
ep = list_entry(item, struct scif_endpt, list);
if (ep->remote_dev->node == node)
__scif_zap_mmaps(ep);
}
mutex_unlock(&scif_info.connlock);
}
/*
* Wrapper for removing remote memory mappings for a particular node. This API
* is called by peer nodes as part of handling a lost node.
*/
void scif_zap_mmaps(int node)
{
_scif_zap_mmaps(node, &scif_info.connected);
_scif_zap_mmaps(node, &scif_info.disconnected);
}
/*
* This API is only called while handling a lost node:
* a) Remote node is dead.
* b) Remote memory mappings have been zapped
* So we can traverse the remote_reg_list without any locks. Since
* the window has not yet been unregistered we can drop the ref count
* and queue it to the cleanup thread.
*/
static void __scif_cleanup_rma_for_zombies(struct scif_endpt *ep)
{
struct list_head *pos, *tmp;
struct scif_window *window;
list_for_each_safe(pos, tmp, &ep->rma_info.remote_reg_list) {
window = list_entry(pos, struct scif_window, list);
if (window->ref_count)
scif_put_window(window, window->nr_pages);
else
dev_err(scif_info.mdev.this_device,
"%s %d unexpected\n",
__func__, __LINE__);
if (!window->ref_count) {
atomic_inc(&ep->rma_info.tw_refcount);
list_del_init(&window->list);
scif_queue_for_cleanup(window, &scif_info.rma);
}
}
}
/* Cleanup remote registration lists for zombie endpoints */
void scif_cleanup_rma_for_zombies(int node)
{
struct scif_endpt *ep;
struct list_head *item;
mutex_lock(&scif_info.eplock);
list_for_each(item, &scif_info.zombie) {
ep = list_entry(item, struct scif_endpt, list);
if (ep->remote_dev && ep->remote_dev->node == node)
__scif_cleanup_rma_for_zombies(ep);
}
mutex_unlock(&scif_info.eplock);
flush_work(&scif_info.misc_work);
}
/* Insert the VMA into the per endpoint VMA list */
static int scif_insert_vma(struct scif_endpt *ep, struct vm_area_struct *vma)
{
struct scif_vma_info *info;
int err = 0;
info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info) {
err = -ENOMEM;
goto done;
}
info->vma = vma;
spin_lock(&ep->lock);
list_add_tail(&info->list, &ep->rma_info.vma_list);
spin_unlock(&ep->lock);
done:
return err;
}
/* Delete the VMA from the per endpoint VMA list */
static void scif_delete_vma(struct scif_endpt *ep, struct vm_area_struct *vma)
{
struct list_head *item;
struct scif_vma_info *info;
spin_lock(&ep->lock);
list_for_each(item, &ep->rma_info.vma_list) {
info = list_entry(item, struct scif_vma_info, list);
if (info->vma == vma) {
list_del(&info->list);
kfree(info);
break;
}
}
spin_unlock(&ep->lock);
}
static phys_addr_t scif_get_phys(phys_addr_t phys, struct scif_endpt *ep)
{
struct scif_dev *scifdev = (struct scif_dev *)ep->remote_dev;
struct scif_hw_dev *sdev = scifdev->sdev;
phys_addr_t out_phys, apt_base = 0;
/*
* If the DMA address is card relative then we need to add the
* aperture base for mmap to work correctly
*/
if (!scifdev_self(scifdev) && sdev->aper && sdev->card_rel_da)
apt_base = sdev->aper->pa;
out_phys = apt_base + phys;
return out_phys;
}
int scif_get_pages(scif_epd_t epd, off_t offset, size_t len,
struct scif_range **pages)
{
struct scif_endpt *ep = (struct scif_endpt *)epd;
struct scif_rma_req req;
struct scif_window *window = NULL;
int nr_pages, err, i;
dev_dbg(scif_info.mdev.this_device,
"SCIFAPI get_pinned_pages: ep %p offset 0x%lx len 0x%lx\n",
ep, offset, len);
err = scif_verify_epd(ep);
if (err)
return err;
if (!len || (offset < 0) ||
(offset + len < offset) ||
(ALIGN(offset, PAGE_SIZE) != offset) ||
(ALIGN(len, PAGE_SIZE) != len))
return -EINVAL;
nr_pages = len >> PAGE_SHIFT;
req.out_window = &window;
req.offset = offset;
req.prot = 0;
req.nr_bytes = len;
req.type = SCIF_WINDOW_SINGLE;
req.head = &ep->rma_info.remote_reg_list;
mutex_lock(&ep->rma_info.rma_lock);
/* Does a valid window exist? */
err = scif_query_window(&req);
if (err) {
dev_err(&ep->remote_dev->sdev->dev,
"%s %d err %d\n", __func__, __LINE__, err);
goto error;
}
/* Allocate scif_range */
*pages = kzalloc(sizeof(**pages), GFP_KERNEL);
if (!*pages) {
err = -ENOMEM;
goto error;
}
/* Allocate phys addr array */
(*pages)->phys_addr = scif_zalloc(nr_pages * sizeof(dma_addr_t));
if (!((*pages)->phys_addr)) {
err = -ENOMEM;
goto error;
}
if (scif_is_mgmt_node() && !scifdev_self(ep->remote_dev)) {
/* Allocate virtual address array */
((*pages)->va = scif_zalloc(nr_pages * sizeof(void *)));
if (!(*pages)->va) {
err = -ENOMEM;
goto error;
}
}
/* Populate the values */
(*pages)->cookie = window;
(*pages)->nr_pages = nr_pages;
(*pages)->prot_flags = window->prot;
for (i = 0; i < nr_pages; i++) {
(*pages)->phys_addr[i] =
__scif_off_to_dma_addr(window, offset +
(i * PAGE_SIZE));
(*pages)->phys_addr[i] = scif_get_phys((*pages)->phys_addr[i],
ep);
if (scif_is_mgmt_node() && !scifdev_self(ep->remote_dev))
(*pages)->va[i] =
ep->remote_dev->sdev->aper->va +
(*pages)->phys_addr[i] -
ep->remote_dev->sdev->aper->pa;
}
scif_get_window(window, nr_pages);
error:
mutex_unlock(&ep->rma_info.rma_lock);
if (err) {
if (*pages) {
scif_free((*pages)->phys_addr,
nr_pages * sizeof(dma_addr_t));
scif_free((*pages)->va,
nr_pages * sizeof(void *));
kfree(*pages);
*pages = NULL;
}
dev_err(&ep->remote_dev->sdev->dev,
"%s %d err %d\n", __func__, __LINE__, err);
}
return err;
}
EXPORT_SYMBOL_GPL(scif_get_pages);
int scif_put_pages(struct scif_range *pages)
{
struct scif_endpt *ep;
struct scif_window *window;
struct scifmsg msg;
if (!pages || !pages->cookie)
return -EINVAL;
window = pages->cookie;
if (!window || window->magic != SCIFEP_MAGIC)
return -EINVAL;
ep = (struct scif_endpt *)window->ep;
/*
* If the state is SCIFEP_CONNECTED or SCIFEP_DISCONNECTED then the
* callee should be allowed to release references to the pages,
* else the endpoint was not connected in the first place,
* hence the ENOTCONN.
*/
if (ep->state != SCIFEP_CONNECTED && ep->state != SCIFEP_DISCONNECTED)
return -ENOTCONN;
mutex_lock(&ep->rma_info.rma_lock);
scif_put_window(window, pages->nr_pages);
/* Initiate window destruction if ref count is zero */
if (!window->ref_count) {
list_del(&window->list);
mutex_unlock(&ep->rma_info.rma_lock);
scif_drain_dma_intr(ep->remote_dev->sdev,
ep->rma_info.dma_chan);
/* Inform the peer about this window being destroyed. */
msg.uop = SCIF_MUNMAP;
msg.src = ep->port;
msg.payload[0] = window->peer_window;
/* No error handling for notification messages */
scif_nodeqp_send(ep->remote_dev, &msg);
/* Destroy this window from the peer's registered AS */
scif_destroy_remote_window(window);
} else {
mutex_unlock(&ep->rma_info.rma_lock);
}
scif_free(pages->phys_addr, pages->nr_pages * sizeof(dma_addr_t));
scif_free(pages->va, pages->nr_pages * sizeof(void *));
kfree(pages);
return 0;
}
EXPORT_SYMBOL_GPL(scif_put_pages);
/*
* scif_rma_list_mmap:
*
* Traverse the remote registration list starting from start_window:
* 1) Create VtoP mappings via remap_pfn_range(..)
* 2) Once step 1) and 2) complete successfully then traverse the range of
* windows again and bump the reference count.
* RMA lock must be held.
*/
static int scif_rma_list_mmap(struct scif_window *start_window, s64 offset,
int nr_pages, struct vm_area_struct *vma)
{
s64 end_offset, loop_offset = offset;
struct scif_window *window = start_window;
int loop_nr_pages, nr_pages_left = nr_pages;
struct scif_endpt *ep = (struct scif_endpt *)start_window->ep;
struct list_head *head = &ep->rma_info.remote_reg_list;
int i, err = 0;
dma_addr_t phys_addr;
struct scif_window_iter src_win_iter;
size_t contig_bytes = 0;
might_sleep();
list_for_each_entry_from(window, head, list) {
end_offset = window->offset +
(window->nr_pages << PAGE_SHIFT);
loop_nr_pages = min_t(int,
(end_offset - loop_offset) >> PAGE_SHIFT,
nr_pages_left);
scif_init_window_iter(window, &src_win_iter);
for (i = 0; i < loop_nr_pages; i++) {
phys_addr = scif_off_to_dma_addr(window, loop_offset,
&contig_bytes,
&src_win_iter);
phys_addr = scif_get_phys(phys_addr, ep);
err = remap_pfn_range(vma,
vma->vm_start +
loop_offset - offset,
phys_addr >> PAGE_SHIFT,
PAGE_SIZE,
vma->vm_page_prot);
if (err)
goto error;
loop_offset += PAGE_SIZE;
}
nr_pages_left -= loop_nr_pages;
if (!nr_pages_left)
break;
}
/*
* No more failures expected. Bump up the ref count for all
* the windows. Another traversal from start_window required
* for handling errors encountered across windows during
* remap_pfn_range(..).
*/
loop_offset = offset;
nr_pages_left = nr_pages;
window = start_window;
head = &ep->rma_info.remote_reg_list;
list_for_each_entry_from(window, head, list) {
end_offset = window->offset +
(window->nr_pages << PAGE_SHIFT);
loop_nr_pages = min_t(int,
(end_offset - loop_offset) >> PAGE_SHIFT,
nr_pages_left);
scif_get_window(window, loop_nr_pages);
nr_pages_left -= loop_nr_pages;
loop_offset += (loop_nr_pages << PAGE_SHIFT);
if (!nr_pages_left)
break;
}
error:
if (err)
dev_err(scif_info.mdev.this_device,
"%s %d err %d\n", __func__, __LINE__, err);
return err;
}
/*
* scif_rma_list_munmap:
*
* Traverse the remote registration list starting from window:
* 1) Decrement ref count.
* 2) If the ref count drops to zero then send a SCIF_MUNMAP message to peer.
* RMA lock must be held.
*/
static void scif_rma_list_munmap(struct scif_window *start_window,
s64 offset, int nr_pages)
{
struct scifmsg msg;
s64 loop_offset = offset, end_offset;
int loop_nr_pages, nr_pages_left = nr_pages;
struct scif_endpt *ep = (struct scif_endpt *)start_window->ep;
struct list_head *head = &ep->rma_info.remote_reg_list;
struct scif_window *window = start_window, *_window;
msg.uop = SCIF_MUNMAP;
msg.src = ep->port;
loop_offset = offset;
nr_pages_left = nr_pages;
list_for_each_entry_safe_from(window, _window, head, list) {
end_offset = window->offset +
(window->nr_pages << PAGE_SHIFT);
loop_nr_pages = min_t(int,
(end_offset - loop_offset) >> PAGE_SHIFT,
nr_pages_left);
scif_put_window(window, loop_nr_pages);
if (!window->ref_count) {
struct scif_dev *rdev = ep->remote_dev;
scif_drain_dma_intr(rdev->sdev,
ep->rma_info.dma_chan);
/* Inform the peer about this munmap */
msg.payload[0] = window->peer_window;
/* No error handling for Notification messages. */
scif_nodeqp_send(ep->remote_dev, &msg);
list_del(&window->list);
/* Destroy this window from the peer's registered AS */
scif_destroy_remote_window(window);
}
nr_pages_left -= loop_nr_pages;
loop_offset += (loop_nr_pages << PAGE_SHIFT);
if (!nr_pages_left)
break;
}
}
/*
* The private data field of each VMA used to mmap a remote window
* points to an instance of struct vma_pvt
*/
struct vma_pvt {
struct scif_endpt *ep; /* End point for remote window */
s64 offset; /* offset within remote window */
bool valid_offset; /* offset is valid only if the original
* mmap request was for a single page
* else the offset within the vma is
* the correct offset
*/
struct kref ref;
};
static void vma_pvt_release(struct kref *ref)
{
struct vma_pvt *vmapvt = container_of(ref, struct vma_pvt, ref);
kfree(vmapvt);
}
/**
* scif_vma_open - VMA open driver callback
* @vma: VMM memory area.
* The open method is called by the kernel to allow the subsystem implementing
* the VMA to initialize the area. This method is invoked any time a new
* reference to the VMA is made (when a process forks, for example).
* The one exception happens when the VMA is first created by mmap;
* in this case, the driver's mmap method is called instead.
* This function is also invoked when an existing VMA is split by the kernel
* due to a call to munmap on a subset of the VMA resulting in two VMAs.
* The kernel invokes this function only on one of the two VMAs.
*/
static void scif_vma_open(struct vm_area_struct *vma)
{
struct vma_pvt *vmapvt = vma->vm_private_data;
dev_dbg(scif_info.mdev.this_device,
"SCIFAPI vma open: vma_start 0x%lx vma_end 0x%lx\n",
vma->vm_start, vma->vm_end);
scif_insert_vma(vmapvt->ep, vma);
kref_get(&vmapvt->ref);
}
/**
* scif_munmap - VMA close driver callback.
* @vma: VMM memory area.
* When an area is destroyed, the kernel calls its close operation.
* Note that there's no usage count associated with VMA's; the area
* is opened and closed exactly once by each process that uses it.
*/
static void scif_munmap(struct vm_area_struct *vma)
{
struct scif_endpt *ep;
struct vma_pvt *vmapvt = vma->vm_private_data;
int nr_pages = vma_pages(vma);
s64 offset;
struct scif_rma_req req;
struct scif_window *window = NULL;
int err;
might_sleep();
dev_dbg(scif_info.mdev.this_device,
"SCIFAPI munmap: vma_start 0x%lx vma_end 0x%lx\n",
vma->vm_start, vma->vm_end);
ep = vmapvt->ep;
offset = vmapvt->valid_offset ? vmapvt->offset :
(vma->vm_pgoff) << PAGE_SHIFT;
dev_dbg(scif_info.mdev.this_device,
"SCIFAPI munmap: ep %p nr_pages 0x%x offset 0x%llx\n",
ep, nr_pages, offset);
req.out_window = &window;
req.offset = offset;
req.nr_bytes = vma->vm_end - vma->vm_start;
req.prot = vma->vm_flags & (VM_READ | VM_WRITE);
req.type = SCIF_WINDOW_PARTIAL;
req.head = &ep->rma_info.remote_reg_list;
mutex_lock(&ep->rma_info.rma_lock);
err = scif_query_window(&req);
if (err)
dev_err(scif_info.mdev.this_device,
"%s %d err %d\n", __func__, __LINE__, err);
else
scif_rma_list_munmap(window, offset, nr_pages);
mutex_unlock(&ep->rma_info.rma_lock);
/*
* The kernel probably zeroes these out but we still want
* to clean up our own mess just in case.
*/
vma->vm_ops = NULL;
vma->vm_private_data = NULL;
kref_put(&vmapvt->ref, vma_pvt_release);
scif_delete_vma(ep, vma);
}
static const struct vm_operations_struct scif_vm_ops = {
.open = scif_vma_open,
.close = scif_munmap,
};
/**
* scif_mmap - Map pages in virtual address space to a remote window.
* @vma: VMM memory area.
* @epd: endpoint descriptor
*
* Return: Upon successful completion, scif_mmap() returns zero
* else an apt error is returned as documented in scif.h
*/
int scif_mmap(struct vm_area_struct *vma, scif_epd_t epd)
{
struct scif_rma_req req;
struct scif_window *window = NULL;
struct scif_endpt *ep = (struct scif_endpt *)epd;
s64 start_offset = vma->vm_pgoff << PAGE_SHIFT;
int nr_pages = vma_pages(vma);
int err;
struct vma_pvt *vmapvt;
dev_dbg(scif_info.mdev.this_device,
"SCIFAPI mmap: ep %p start_offset 0x%llx nr_pages 0x%x\n",
ep, start_offset, nr_pages);
err = scif_verify_epd(ep);
if (err)
return err;
might_sleep();
err = scif_insert_vma(ep, vma);
if (err)
return err;
vmapvt = kzalloc(sizeof(*vmapvt), GFP_KERNEL);
if (!vmapvt) {
scif_delete_vma(ep, vma);
return -ENOMEM;
}
vmapvt->ep = ep;
kref_init(&vmapvt->ref);
req.out_window = &window;
req.offset = start_offset;
req.nr_bytes = vma->vm_end - vma->vm_start;
req.prot = vma->vm_flags & (VM_READ | VM_WRITE);
req.type = SCIF_WINDOW_PARTIAL;
req.head = &ep->rma_info.remote_reg_list;
mutex_lock(&ep->rma_info.rma_lock);
/* Does a valid window exist? */
err = scif_query_window(&req);
if (err) {
dev_err(&ep->remote_dev->sdev->dev,
"%s %d err %d\n", __func__, __LINE__, err);
goto error_unlock;
}
/* Default prot for loopback */
if (!scifdev_self(ep->remote_dev))
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
/*
* VM_DONTCOPY - Do not copy this vma on fork
* VM_DONTEXPAND - Cannot expand with mremap()
* VM_RESERVED - Count as reserved_vm like IO
* VM_PFNMAP - Page-ranges managed without "struct page"
* VM_IO - Memory mapped I/O or similar
*
* We do not want to copy this VMA automatically on a fork(),
* expand this VMA due to mremap() or swap out these pages since
* the VMA is actually backed by physical pages in the remote
* node's physical memory and not via a struct page.
*/
vma->vm_flags |= VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP;
if (!scifdev_self(ep->remote_dev))
vma->vm_flags |= VM_IO | VM_PFNMAP;
/* Map this range of windows */
err = scif_rma_list_mmap(window, start_offset, nr_pages, vma);
if (err) {
dev_err(&ep->remote_dev->sdev->dev,
"%s %d err %d\n", __func__, __LINE__, err);
goto error_unlock;
}
/* Set up the driver call back */
vma->vm_ops = &scif_vm_ops;
vma->vm_private_data = vmapvt;
error_unlock:
mutex_unlock(&ep->rma_info.rma_lock);
if (err) {
kfree(vmapvt);
dev_err(&ep->remote_dev->sdev->dev,
"%s %d err %d\n", __func__, __LINE__, err);
scif_delete_vma(ep, vma);
}
return err;
}