1// SPDX-License-Identifier: GPL-2.0
2
3/*
4 * Copyright 2022 HabanaLabs, Ltd.
5 * All Rights Reserved.
6 */
7
8#include "habanalabs.h"
9
10/**
11 * hl_mmap_mem_buf_get - increase the buffer refcount and return a pointer to
12 *                        the buffer descriptor.
13 *
14 * @mmg: parent unified memory manager
15 * @handle: requested buffer handle
16 *
17 * Find the buffer in the store and return a pointer to its descriptor.
18 * Increase buffer refcount. If not found - return NULL.
19 */
20struct hl_mmap_mem_buf *hl_mmap_mem_buf_get(struct hl_mem_mgr *mmg, u64 handle)
21{
22	struct hl_mmap_mem_buf *buf;
23
24	spin_lock(&mmg->lock);
25	buf = idr_find(&mmg->handles, lower_32_bits(handle >> PAGE_SHIFT));
26	if (!buf) {
27		spin_unlock(&mmg->lock);
28		dev_dbg(mmg->dev, "Buff get failed, no match to handle %#llx\n", handle);
29		return NULL;
30	}
31	kref_get(&buf->refcount);
32	spin_unlock(&mmg->lock);
33	return buf;
34}
35
36/**
37 * hl_mmap_mem_buf_destroy - destroy the unused buffer
38 *
39 * @buf: memory manager buffer descriptor
40 *
41 * Internal function, used as a final step of buffer release. Shall be invoked
42 * only when the buffer is no longer in use (removed from idr). Will call the
43 * release callback (if applicable), and free the memory.
44 */
45static void hl_mmap_mem_buf_destroy(struct hl_mmap_mem_buf *buf)
46{
47	if (buf->behavior->release)
48		buf->behavior->release(buf);
49
50	kfree(buf);
51}
52
53/**
54 * hl_mmap_mem_buf_release - release buffer
55 *
56 * @kref: kref that reached 0.
57 *
58 * Internal function, used as a kref release callback, when the last user of
59 * the buffer is released. Shall be called from an interrupt context.
60 */
61static void hl_mmap_mem_buf_release(struct kref *kref)
62{
63	struct hl_mmap_mem_buf *buf =
64		container_of(kref, struct hl_mmap_mem_buf, refcount);
65
66	spin_lock(&buf->mmg->lock);
67	idr_remove(&buf->mmg->handles, lower_32_bits(buf->handle >> PAGE_SHIFT));
68	spin_unlock(&buf->mmg->lock);
69
70	hl_mmap_mem_buf_destroy(buf);
71}
72
73/**
74 * hl_mmap_mem_buf_remove_idr_locked - remove handle from idr
75 *
76 * @kref: kref that reached 0.
77 *
78 * Internal function, used for kref put by handle. Assumes mmg lock is taken.
79 * Will remove the buffer from idr, without destroying it.
80 */
81static void hl_mmap_mem_buf_remove_idr_locked(struct kref *kref)
82{
83	struct hl_mmap_mem_buf *buf =
84		container_of(kref, struct hl_mmap_mem_buf, refcount);
85
86	idr_remove(&buf->mmg->handles, lower_32_bits(buf->handle >> PAGE_SHIFT));
87}
88
89/**
90 * hl_mmap_mem_buf_put - decrease the reference to the buffer
91 *
92 * @buf: memory manager buffer descriptor
93 *
94 * Decrease the reference to the buffer, and release it if it was the last one.
95 * Shall be called from an interrupt context.
96 */
97int hl_mmap_mem_buf_put(struct hl_mmap_mem_buf *buf)
98{
99	return kref_put(&buf->refcount, hl_mmap_mem_buf_release);
100}
101
102/**
103 * hl_mmap_mem_buf_put_handle - decrease the reference to the buffer with the
104 *                              given handle.
105 *
106 * @mmg: parent unified memory manager
107 * @handle: requested buffer handle
108 *
109 * Decrease the reference to the buffer, and release it if it was the last one.
110 * Shall not be called from an interrupt context. Return -EINVAL if handle was
111 * not found, else return the put outcome (0 or 1).
112 */
113int hl_mmap_mem_buf_put_handle(struct hl_mem_mgr *mmg, u64 handle)
114{
115	struct hl_mmap_mem_buf *buf;
116
117	spin_lock(&mmg->lock);
118	buf = idr_find(&mmg->handles, lower_32_bits(handle >> PAGE_SHIFT));
119	if (!buf) {
120		spin_unlock(&mmg->lock);
121		dev_dbg(mmg->dev,
122			 "Buff put failed, no match to handle %#llx\n", handle);
123		return -EINVAL;
124	}
125
126	if (kref_put(&buf->refcount, hl_mmap_mem_buf_remove_idr_locked)) {
127		spin_unlock(&mmg->lock);
128		hl_mmap_mem_buf_destroy(buf);
129		return 1;
130	}
131
132	spin_unlock(&mmg->lock);
133	return 0;
134}
135
136/**
137 * hl_mmap_mem_buf_alloc - allocate a new mappable buffer
138 *
139 * @mmg: parent unified memory manager
140 * @behavior: behavior object describing this buffer polymorphic behavior
141 * @gfp: gfp flags to use for the memory allocations
142 * @args: additional args passed to behavior->alloc
143 *
144 * Allocate and register a new memory buffer inside the give memory manager.
145 * Return the pointer to the new buffer on success or NULL on failure.
146 */
147struct hl_mmap_mem_buf *
148hl_mmap_mem_buf_alloc(struct hl_mem_mgr *mmg,
149		      struct hl_mmap_mem_buf_behavior *behavior, gfp_t gfp,
150		      void *args)
151{
152	struct hl_mmap_mem_buf *buf;
153	int rc;
154
155	buf = kzalloc(sizeof(*buf), gfp);
156	if (!buf)
157		return NULL;
158
159	spin_lock(&mmg->lock);
160	rc = idr_alloc(&mmg->handles, buf, 1, 0, GFP_ATOMIC);
161	spin_unlock(&mmg->lock);
162	if (rc < 0) {
163		dev_err(mmg->dev,
164			"%s: Failed to allocate IDR for a new buffer, rc=%d\n",
165			behavior->topic, rc);
166		goto free_buf;
167	}
168
169	buf->mmg = mmg;
170	buf->behavior = behavior;
171	buf->handle = (((u64)rc | buf->behavior->mem_id) << PAGE_SHIFT);
172	kref_init(&buf->refcount);
173
174	rc = buf->behavior->alloc(buf, gfp, args);
175	if (rc) {
176		dev_err(mmg->dev, "%s: Failure in buffer alloc callback %d\n",
177			behavior->topic, rc);
178		goto remove_idr;
179	}
180
181	return buf;
182
183remove_idr:
184	spin_lock(&mmg->lock);
185	idr_remove(&mmg->handles, lower_32_bits(buf->handle >> PAGE_SHIFT));
186	spin_unlock(&mmg->lock);
187free_buf:
188	kfree(buf);
189	return NULL;
190}
191
192/**
193 * hl_mmap_mem_buf_vm_close - handle mmap close
194 *
195 * @vma: the vma object for which mmap was closed.
196 *
197 * Put the memory buffer if it is no longer mapped.
198 */
199static void hl_mmap_mem_buf_vm_close(struct vm_area_struct *vma)
200{
201	struct hl_mmap_mem_buf *buf =
202		(struct hl_mmap_mem_buf *)vma->vm_private_data;
203	long new_mmap_size;
204
205	new_mmap_size = buf->real_mapped_size - (vma->vm_end - vma->vm_start);
206
207	if (new_mmap_size > 0) {
208		buf->real_mapped_size = new_mmap_size;
209		return;
210	}
211
212	atomic_set(&buf->mmap, 0);
213	hl_mmap_mem_buf_put(buf);
214	vma->vm_private_data = NULL;
215}
216
217static const struct vm_operations_struct hl_mmap_mem_buf_vm_ops = {
218	.close = hl_mmap_mem_buf_vm_close
219};
220
221/**
222 * hl_mem_mgr_mmap - map the given buffer to the user
223 *
224 * @mmg: unified memory manager
225 * @vma: the vma object for which mmap was closed.
226 * @args: additional args passed to behavior->mmap
227 *
228 * Map the buffer specified by the vma->vm_pgoff to the given vma.
229 */
230int hl_mem_mgr_mmap(struct hl_mem_mgr *mmg, struct vm_area_struct *vma,
231		    void *args)
232{
233	struct hl_mmap_mem_buf *buf;
234	u64 user_mem_size;
235	u64 handle;
236	int rc;
237
238	/* We use the page offset to hold the idr and thus we need to clear
239	 * it before doing the mmap itself
240	 */
241	handle = vma->vm_pgoff << PAGE_SHIFT;
242	vma->vm_pgoff = 0;
243
244	/* Reference was taken here */
245	buf = hl_mmap_mem_buf_get(mmg, handle);
246	if (!buf) {
247		dev_err(mmg->dev,
248			"Memory mmap failed, no match to handle %#llx\n", handle);
249		return -EINVAL;
250	}
251
252	/* Validation check */
253	user_mem_size = vma->vm_end - vma->vm_start;
254	if (user_mem_size != ALIGN(buf->mappable_size, PAGE_SIZE)) {
255		dev_err(mmg->dev,
256			"%s: Memory mmap failed, mmap VM size 0x%llx != 0x%llx allocated physical mem size\n",
257			buf->behavior->topic, user_mem_size, buf->mappable_size);
258		rc = -EINVAL;
259		goto put_mem;
260	}
261
262#ifdef _HAS_TYPE_ARG_IN_ACCESS_OK
263	if (!access_ok(VERIFY_WRITE, (void __user *)(uintptr_t)vma->vm_start,
264		       user_mem_size)) {
265#else
266	if (!access_ok((void __user *)(uintptr_t)vma->vm_start,
267		       user_mem_size)) {
268#endif
269		dev_err(mmg->dev, "%s: User pointer is invalid - 0x%lx\n",
270			buf->behavior->topic, vma->vm_start);
271
272		rc = -EINVAL;
273		goto put_mem;
274	}
275
276	if (atomic_cmpxchg(&buf->mmap, 0, 1)) {
277		dev_err(mmg->dev,
278			"%s, Memory mmap failed, already mapped to user\n",
279			buf->behavior->topic);
280		rc = -EINVAL;
281		goto put_mem;
282	}
283
284	vma->vm_ops = &hl_mmap_mem_buf_vm_ops;
285
286	/* Note: We're transferring the memory reference to vma->vm_private_data here. */
287
288	vma->vm_private_data = buf;
289
290	rc = buf->behavior->mmap(buf, vma, args);
291	if (rc) {
292		atomic_set(&buf->mmap, 0);
293		goto put_mem;
294	}
295
296	buf->real_mapped_size = buf->mappable_size;
297	vma->vm_pgoff = handle >> PAGE_SHIFT;
298
299	return 0;
300
301put_mem:
302	hl_mmap_mem_buf_put(buf);
303	return rc;
304}
305
306/**
307 * hl_mem_mgr_init - initialize unified memory manager
308 *
309 * @dev: owner device pointer
310 * @mmg: structure to initialize
311 *
312 * Initialize an instance of unified memory manager
313 */
314void hl_mem_mgr_init(struct device *dev, struct hl_mem_mgr *mmg)
315{
316	mmg->dev = dev;
317	spin_lock_init(&mmg->lock);
318	idr_init(&mmg->handles);
319}
320
321/**
322 * hl_mem_mgr_fini - release unified memory manager
323 *
324 * @mmg: parent unified memory manager
325 *
326 * Release the unified memory manager. Shall be called from an interrupt context.
327 */
328void hl_mem_mgr_fini(struct hl_mem_mgr *mmg)
329{
330	struct hl_mmap_mem_buf *buf;
331	struct idr *idp;
332	const char *topic;
333	u32 id;
334
335	idp = &mmg->handles;
336
337	idr_for_each_entry(idp, buf, id) {
338		topic = buf->behavior->topic;
339		if (hl_mmap_mem_buf_put(buf) != 1)
340			dev_err(mmg->dev,
341				"%s: Buff handle %u for CTX is still alive\n",
342				topic, id);
343	}
344}
345
346/**
347 * hl_mem_mgr_idr_destroy() - destroy memory manager IDR.
348 * @mmg: parent unified memory manager
349 *
350 * Destroy the memory manager IDR.
351 * Shall be called when IDR is empty and no memory buffers are in use.
352 */
353void hl_mem_mgr_idr_destroy(struct hl_mem_mgr *mmg)
354{
355	if (!idr_is_empty(&mmg->handles))
356		dev_crit(mmg->dev, "memory manager IDR is destroyed while it is not empty!\n");
357
358	idr_destroy(&mmg->handles);
359}
360