1 // SPDX-License-Identifier: GPL-2.0 OR MIT
2 
3 /*
4  * Xen para-virtual sound device
5  *
6  * Copyright (C) 2016-2018 EPAM Systems Inc.
7  *
8  * Author: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
9  */
10 
11 #include <linux/kernel.h>
12 #include <xen/xen.h>
13 #include <xen/xenbus.h>
14 
15 #include "xen_snd_front_shbuf.h"
16 
xen_snd_front_shbuf_get_dir_start(struct xen_snd_front_shbuf * buf)17 grant_ref_t xen_snd_front_shbuf_get_dir_start(struct xen_snd_front_shbuf *buf)
18 {
19 	if (!buf->grefs)
20 		return GRANT_INVALID_REF;
21 
22 	return buf->grefs[0];
23 }
24 
xen_snd_front_shbuf_clear(struct xen_snd_front_shbuf * buf)25 void xen_snd_front_shbuf_clear(struct xen_snd_front_shbuf *buf)
26 {
27 	memset(buf, 0, sizeof(*buf));
28 }
29 
xen_snd_front_shbuf_free(struct xen_snd_front_shbuf * buf)30 void xen_snd_front_shbuf_free(struct xen_snd_front_shbuf *buf)
31 {
32 	int i;
33 
34 	if (buf->grefs) {
35 		for (i = 0; i < buf->num_grefs; i++)
36 			if (buf->grefs[i] != GRANT_INVALID_REF)
37 				gnttab_end_foreign_access(buf->grefs[i],
38 							  0, 0UL);
39 		kfree(buf->grefs);
40 	}
41 	kfree(buf->directory);
42 	free_pages_exact(buf->buffer, buf->buffer_sz);
43 	xen_snd_front_shbuf_clear(buf);
44 }
45 
46 /*
47  * number of grant references a page can hold with respect to the
48  * xensnd_page_directory header
49  */
50 #define XENSND_NUM_GREFS_PER_PAGE ((XEN_PAGE_SIZE - \
51 		offsetof(struct xensnd_page_directory, gref)) / \
52 		sizeof(grant_ref_t))
53 
fill_page_dir(struct xen_snd_front_shbuf * buf,int num_pages_dir)54 static void fill_page_dir(struct xen_snd_front_shbuf *buf,
55 			  int num_pages_dir)
56 {
57 	struct xensnd_page_directory *page_dir;
58 	unsigned char *ptr;
59 	int i, cur_gref, grefs_left, to_copy;
60 
61 	ptr = buf->directory;
62 	grefs_left = buf->num_grefs - num_pages_dir;
63 	/*
64 	 * skip grant references at the beginning, they are for pages granted
65 	 * for the page directory itself
66 	 */
67 	cur_gref = num_pages_dir;
68 	for (i = 0; i < num_pages_dir; i++) {
69 		page_dir = (struct xensnd_page_directory *)ptr;
70 		if (grefs_left <= XENSND_NUM_GREFS_PER_PAGE) {
71 			to_copy = grefs_left;
72 			page_dir->gref_dir_next_page = GRANT_INVALID_REF;
73 		} else {
74 			to_copy = XENSND_NUM_GREFS_PER_PAGE;
75 			page_dir->gref_dir_next_page = buf->grefs[i + 1];
76 		}
77 
78 		memcpy(&page_dir->gref, &buf->grefs[cur_gref],
79 		       to_copy * sizeof(grant_ref_t));
80 
81 		ptr += XEN_PAGE_SIZE;
82 		grefs_left -= to_copy;
83 		cur_gref += to_copy;
84 	}
85 }
86 
grant_references(struct xenbus_device * xb_dev,struct xen_snd_front_shbuf * buf,int num_pages_dir,int num_pages_buffer,int num_grefs)87 static int grant_references(struct xenbus_device *xb_dev,
88 			    struct xen_snd_front_shbuf *buf,
89 			    int num_pages_dir, int num_pages_buffer,
90 			    int num_grefs)
91 {
92 	grant_ref_t priv_gref_head;
93 	unsigned long frame;
94 	int ret, i, j, cur_ref;
95 	int otherend_id;
96 
97 	ret = gnttab_alloc_grant_references(num_grefs, &priv_gref_head);
98 	if (ret)
99 		return ret;
100 
101 	buf->num_grefs = num_grefs;
102 	otherend_id = xb_dev->otherend_id;
103 	j = 0;
104 
105 	for (i = 0; i < num_pages_dir; i++) {
106 		cur_ref = gnttab_claim_grant_reference(&priv_gref_head);
107 		if (cur_ref < 0) {
108 			ret = cur_ref;
109 			goto fail;
110 		}
111 
112 		frame = xen_page_to_gfn(virt_to_page(buf->directory +
113 						     XEN_PAGE_SIZE * i));
114 		gnttab_grant_foreign_access_ref(cur_ref, otherend_id, frame, 0);
115 		buf->grefs[j++] = cur_ref;
116 	}
117 
118 	for (i = 0; i < num_pages_buffer; i++) {
119 		cur_ref = gnttab_claim_grant_reference(&priv_gref_head);
120 		if (cur_ref < 0) {
121 			ret = cur_ref;
122 			goto fail;
123 		}
124 
125 		frame = xen_page_to_gfn(virt_to_page(buf->buffer +
126 						     XEN_PAGE_SIZE * i));
127 		gnttab_grant_foreign_access_ref(cur_ref, otherend_id, frame, 0);
128 		buf->grefs[j++] = cur_ref;
129 	}
130 
131 	gnttab_free_grant_references(priv_gref_head);
132 	fill_page_dir(buf, num_pages_dir);
133 	return 0;
134 
135 fail:
136 	gnttab_free_grant_references(priv_gref_head);
137 	return ret;
138 }
139 
alloc_int_buffers(struct xen_snd_front_shbuf * buf,int num_pages_dir,int num_pages_buffer,int num_grefs)140 static int alloc_int_buffers(struct xen_snd_front_shbuf *buf,
141 			     int num_pages_dir, int num_pages_buffer,
142 			     int num_grefs)
143 {
144 	buf->grefs = kcalloc(num_grefs, sizeof(*buf->grefs), GFP_KERNEL);
145 	if (!buf->grefs)
146 		return -ENOMEM;
147 
148 	buf->directory = kcalloc(num_pages_dir, XEN_PAGE_SIZE, GFP_KERNEL);
149 	if (!buf->directory)
150 		goto fail;
151 
152 	buf->buffer_sz = num_pages_buffer * XEN_PAGE_SIZE;
153 	buf->buffer = alloc_pages_exact(buf->buffer_sz, GFP_KERNEL);
154 	if (!buf->buffer)
155 		goto fail;
156 
157 	return 0;
158 
159 fail:
160 	kfree(buf->grefs);
161 	buf->grefs = NULL;
162 	kfree(buf->directory);
163 	buf->directory = NULL;
164 	return -ENOMEM;
165 }
166 
xen_snd_front_shbuf_alloc(struct xenbus_device * xb_dev,struct xen_snd_front_shbuf * buf,unsigned int buffer_sz)167 int xen_snd_front_shbuf_alloc(struct xenbus_device *xb_dev,
168 			      struct xen_snd_front_shbuf *buf,
169 			      unsigned int buffer_sz)
170 {
171 	int num_pages_buffer, num_pages_dir, num_grefs;
172 	int ret;
173 
174 	xen_snd_front_shbuf_clear(buf);
175 
176 	num_pages_buffer = DIV_ROUND_UP(buffer_sz, XEN_PAGE_SIZE);
177 	/* number of pages the page directory consumes itself */
178 	num_pages_dir = DIV_ROUND_UP(num_pages_buffer,
179 				     XENSND_NUM_GREFS_PER_PAGE);
180 	num_grefs = num_pages_buffer + num_pages_dir;
181 
182 	ret = alloc_int_buffers(buf, num_pages_dir,
183 				num_pages_buffer, num_grefs);
184 	if (ret < 0)
185 		return ret;
186 
187 	ret = grant_references(xb_dev, buf, num_pages_dir, num_pages_buffer,
188 			       num_grefs);
189 	if (ret < 0)
190 		return ret;
191 
192 	fill_page_dir(buf, num_pages_dir);
193 	return 0;
194 }
195