diff --git a/drivers/xen/gntalloc.c b/drivers/xen/gntalloc.c index 787d179..60998d1 100644 --- a/drivers/xen/gntalloc.c +++ b/drivers/xen/gntalloc.c @@ -85,6 +85,12 @@ struct notify_info { int event; /* Port (event channel) to notify */ }; +/* list of foreign grants associated to a given grant reference */ +struct gntalloc_share_list { + grant_ref_t gref_id; + struct list_head next_share; +}; + /* Metadata on a grant reference. */ struct gntalloc_gref { struct list_head next_gref; /* list entry gref_list */ @@ -92,7 +98,7 @@ struct gntalloc_gref { struct page *page; /* The shared page */ uint64_t file_index; /* File offset for mmap() */ unsigned int users; /* Use count - when zero, waiting on Xen */ - grant_ref_t gref_id; /* The grant reference number */ + struct gntalloc_share_list share_list; /* The list of grant reference numbers associated with this page */ struct notify_info notify; /* Unmap notification */ }; @@ -125,6 +131,7 @@ static int add_grefs(struct ioctl_gntalloc_alloc_gref *op, LIST_HEAD(queue_gref); LIST_HEAD(queue_file); struct gntalloc_gref *gref; + grant_ref_t gref_id; readonly = !(op->flags & GNTALLOC_FLAG_WRITABLE); rc = -ENOMEM; @@ -141,13 +148,19 @@ static int add_grefs(struct ioctl_gntalloc_alloc_gref *op, goto undo; /* Grant foreign access to the page. */ - gref->gref_id = gnttab_grant_foreign_access(op->domid, + gref_id = gnttab_grant_foreign_access(op->domid, pfn_to_mfn(page_to_pfn(gref->page)), readonly); - if ((int)gref->gref_id < 0) { - rc = gref->gref_id; + if ((int)gref_id < 0) { + rc = gref_id; goto undo; } - gref_ids[i] = gref->gref_id; + + // store the gred_id to be returned to the user so they can use it later + gref_ids[i] = gref_id; + + // add this gref to the list for this page + INIT_LIST_HEAD(&gref->share_list.next_share); + gref->share_list.gref_id = gref_id; } /* Add to gref lists. */ @@ -179,8 +192,21 @@ undo: return rc; } +static void __release_gref(grant_ref_t *gref_id) +{ + if ((*gref_id > 0) && + !gnttab_query_foreign_access(*gref_id) && + gnttab_end_foreign_access_ref(*gref_id, 0)) + { + gnttab_free_grant_reference(*gref_id); + *gref_id = 0; + } +} + static void __del_gref(struct gntalloc_gref *gref) { + struct gntalloc_share_list *pos = NULL; + if (gref->notify.flags & UNMAP_NOTIFY_CLEAR_BYTE) { uint8_t *tmp = kmap(gref->page); tmp[gref->notify.pgoff] = 0; @@ -192,16 +218,16 @@ static void __del_gref(struct gntalloc_gref *gref) } gref->notify.flags = 0; - - if (gref->gref_id > 0) { - if (gnttab_query_foreign_access(gref->gref_id)) - return; - - if (!gnttab_end_foreign_access_ref(gref->gref_id, 0)) - return; - - gnttab_free_grant_reference(gref->gref_id); + while (!list_empty(&gref->share_list.next_share)) + { + pos = list_first_entry(&gref->share_list.next_share, + struct gntalloc_share_list, + next_share); + list_del(&pos->next_share); + __release_gref(&pos->gref_id); + kfree(pos); } + __release_gref(&gref->share_list.gref_id); gref_size--; list_del(&gref->next_gref); @@ -337,6 +363,7 @@ static long gntalloc_ioctl_alloc(struct gntalloc_file_private_data *priv, out_free: kfree(gref_ids); + out: return rc; } @@ -435,6 +462,97 @@ static long gntalloc_ioctl_unmap_notify(struct gntalloc_file_private_data *priv, return rc; } +static long gntalloc_ioctl_share(struct gntalloc_file_private_data *priv, void __user *arg) +{ + struct ioctl_gntalloc_share_gref op; + struct gntalloc_gref *gref; + int rc; + int readonly; + grant_ref_t gref_id; + struct gntalloc_share_list *share_list; + + if (copy_from_user(&op, arg, sizeof(op))) + return -EFAULT; + + readonly = !(op.flags & GNTALLOC_FLAG_WRITABLE); + rc = -ENOMEM; + + /* get the grant structure */ + mutex_lock(&gref_mutex); + gref = find_grefs(priv, op.index, 1); + + if (!gref) { + rc = -ENOENT; + goto share_out; + } + + /* Grant foreign access to the page. */ + gref_id = gnttab_grant_foreign_access(op.domid, + pfn_to_mfn(page_to_pfn(gref->page)), readonly); + if ((signed)gref_id < 0) { + rc = gref_id; + goto share_out; + } + + // add this gref to the list for this page + share_list = kzalloc(sizeof(*share_list), GFP_KERNEL); + INIT_LIST_HEAD(&share_list->next_share); + share_list->gref_id = gref_id; + list_add_tail(&share_list->next_share, &gref->share_list.next_share); + + // set output data + op.gref_id = gref_id; + rc = 0; + +share_out: + mutex_unlock(&gref_mutex); + + if (!rc && copy_to_user(arg, &op, sizeof(op))) + rc = -EFAULT; + + return rc; +} + +static long gntalloc_ioctl_unshare(struct gntalloc_file_private_data *priv, void __user *arg) +{ + struct ioctl_gntalloc_unshare_gref op; + struct gntalloc_gref *gref; + int rc = -ENOENT; + struct gntalloc_share_list *pos; + + if (copy_from_user(&op, arg, sizeof(op))) + return -EFAULT; + + /* get the grant structure */ + mutex_lock(&gref_mutex); + gref = find_grefs(priv, op.index, 1); + + if (!gref) + goto unshare_out; + + // search the share list for this grant ref + list_for_each_entry(pos, &gref->share_list.next_share, next_share) { + // if this is our grant ref then delete it + if (pos->gref_id == op.gref_id) { + // delete + list_del(&pos->next_share); + __release_gref(&pos->gref_id); + kfree(pos); + + // success + rc = 0; + + // done + break; + } + } + +unshare_out: + mutex_unlock(&gref_mutex); + + return rc; +} + static long gntalloc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { @@ -450,6 +568,12 @@ static long gntalloc_ioctl(struct file *filp, unsigned int cmd, case IOCTL_GNTALLOC_SET_UNMAP_NOTIFY: return gntalloc_ioctl_unmap_notify(priv, (void __user *)arg); + case IOCTL_GNTALLOC_SHARE_GREF: + return gntalloc_ioctl_share(priv, (void __user *)arg); + + case IOCTL_GNTALLOC_UNSHARE_GREF: + return gntalloc_ioctl_unshare(priv, (void __user *)arg); + default: return -ENOIOCTLCMD; } diff --git a/include/uapi/xen/gntalloc.h b/include/uapi/xen/gntalloc.h index 76bd580..20dc973 100644 --- a/include/uapi/xen/gntalloc.h +++ b/include/uapi/xen/gntalloc.h @@ -79,4 +79,36 @@ struct ioctl_gntalloc_unmap_notify { /* Send an interrupt on the indicated event channel */ #define UNMAP_NOTIFY_SEND_EVENT 0x2 +/* + * Shares a page that has already been allocated (so we can share the smae page + * between more than one domain) + */ +#define IOCTL_GNTALLOC_SHARE_GREF \ +_IOC(_IOC_NONE, 'G', 8, sizeof(struct ioctl_gntalloc_share_gref)) +struct ioctl_gntalloc_share_gref { + /* IN parameters */ + /* The ID of the domain to be given access to the grants. */ + uint16_t domid; + /* Flags for this mapping */ + uint16_t flags; + /* The offset used in call to mmap(). */ + uint64_t index; + /* OUT parameters */ + /* The grant references of the newly created grant */ + uint32_t gref_id; +}; + +/* + * Unhares a page that has already been shared + */ +#define IOCTL_GNTALLOC_UNSHARE_GREF \ +_IOC(_IOC_NONE, 'G', 9, sizeof(struct ioctl_gntalloc_unshare_gref)) +struct ioctl_gntalloc_unshare_gref { + /* IN parameters */ + /* The offset used in call to mmap(). */ + uint64_t index; + /* The grant references of the newly created grant */ + uint32_t gref_id; +}; + #endif /* __LINUX_PUBLIC_GNTALLOC_H__ */