summaryrefslogtreecommitdiff
path: root/drivers/misc/habanalabs/common/memory_mgr.c
blob: ea5f2bd31b0a826328e29bed06a7082d4600898f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright 2022 HabanaLabs, Ltd.
 * All Rights Reserved.
 */

#include "habanalabs.h"

/**
 * hl_mmap_mem_buf_get - increase the buffer refcount and return a pointer to
 *                        the buffer descriptor.
 *
 * @mmg: parent unifed memory manager
 * @handle: requested buffer handle
 *
 * Find the buffer in the store and return a pointer to its descriptor.
 * Increase buffer refcount. If not found - return NULL.
 */
struct hl_mmap_mem_buf *hl_mmap_mem_buf_get(struct hl_mem_mgr *mmg, u64 handle)
{
	struct hl_mmap_mem_buf *buf;

	spin_lock(&mmg->lock);
	buf = idr_find(&mmg->handles, lower_32_bits(handle >> PAGE_SHIFT));
	if (!buf) {
		spin_unlock(&mmg->lock);
		dev_warn(mmg->dev,
			 "Buff get failed, no match to handle %#llx\n", handle);
		return NULL;
	}
	kref_get(&buf->refcount);
	spin_unlock(&mmg->lock);
	return buf;
}

/**
 * hl_mmap_mem_buf_destroy - destroy the unused buffer
 *
 * @buf: memory manager buffer descriptor
 *
 * Internal function, used as a final step of buffer release. Shall be invoked
 * only when the buffer is no longer in use (removed from idr). Will call the
 * release callback (if applicable), and free the memory.
 */
static void hl_mmap_mem_buf_destroy(struct hl_mmap_mem_buf *buf)
{
	if (buf->behavior->release)
		buf->behavior->release(buf);

	kfree(buf);
}

/**
 * hl_mmap_mem_buf_release - release buffer
 *
 * @kref: kref that reached 0.
 *
 * Internal function, used as a kref release callback, when the last user of
 * the buffer is released. Shall be called from an interrupt context.
 */
static void hl_mmap_mem_buf_release(struct kref *kref)
{
	struct hl_mmap_mem_buf *buf =
		container_of(kref, struct hl_mmap_mem_buf, refcount);

	spin_lock(&buf->mmg->lock);
	idr_remove(&buf->mmg->handles, lower_32_bits(buf->handle >> PAGE_SHIFT));
	spin_unlock(&buf->mmg->lock);

	hl_mmap_mem_buf_destroy(buf);
}

/**
 * hl_mmap_mem_buf_remove_idr_locked - remove handle from idr
 *
 * @kref: kref that reached 0.
 *
 * Internal function, used for kref put by handle. Assumes mmg lock is taken.
 * Will remove the buffer from idr, without destroying it.
 */
static void hl_mmap_mem_buf_remove_idr_locked(struct kref *kref)
{
	struct hl_mmap_mem_buf *buf =
		container_of(kref, struct hl_mmap_mem_buf, refcount);

	idr_remove(&buf->mmg->handles, lower_32_bits(buf->handle >> PAGE_SHIFT));
}

/**
 * hl_mmap_mem_buf_put - decrease the reference to the buffer
 *
 * @buf: memory manager buffer descriptor
 *
 * Decrease the reference to the buffer, and release it if it was the last one.
 * Shall be called from an interrupt context.
 */
int hl_mmap_mem_buf_put(struct hl_mmap_mem_buf *buf)
{
	return kref_put(&buf->refcount, hl_mmap_mem_buf_release);
}

/**
 * hl_mmap_mem_buf_put_handle - decrease the reference to the buffer with the
 *                              given handle.
 *
 * @mmg: parent unifed memory manager
 * @handle: requested buffer handle
 *
 * Decrease the reference to the buffer, and release it if it was the last one.
 * Shall not be called from an interrupt context. Return -EINVAL if handle was
 * not found, else return the put outcome (0 or 1).
 */
int hl_mmap_mem_buf_put_handle(struct hl_mem_mgr *mmg, u64 handle)
{
	struct hl_mmap_mem_buf *buf;

	spin_lock(&mmg->lock);
	buf = idr_find(&mmg->handles, lower_32_bits(handle >> PAGE_SHIFT));
	if (!buf) {
		spin_unlock(&mmg->lock);
		dev_dbg(mmg->dev,
			 "Buff put failed, no match to handle %#llx\n", handle);
		return -EINVAL;
	}

	if (kref_put(&buf->refcount, hl_mmap_mem_buf_remove_idr_locked)) {
		spin_unlock(&mmg->lock);
		hl_mmap_mem_buf_destroy(buf);
		return 1;
	}

	spin_unlock(&mmg->lock);
	return 0;
}

/**
 * @hl_mmap_mem_buf_alloc - allocate a new mappable buffer
 *
 * @mmg: parent unifed memory manager
 * @behavior: behavior object describing this buffer polymorphic behavior
 * @gfp: gfp flags to use for the memory allocations
 * @args: additional args passed to behavior->alloc
 *
 * Allocate and register a new memory buffer inside the give memory manager.
 * Return the pointer to the new buffer on success or NULL on failure.
 */
struct hl_mmap_mem_buf *
hl_mmap_mem_buf_alloc(struct hl_mem_mgr *mmg,
		      struct hl_mmap_mem_buf_behavior *behavior, gfp_t gfp,
		      void *args)
{
	struct hl_mmap_mem_buf *buf;
	int rc;

	buf = kzalloc(sizeof(*buf), gfp);
	if (!buf)
		return NULL;

	spin_lock(&mmg->lock);
	rc = idr_alloc(&mmg->handles, buf, 1, 0, GFP_ATOMIC);
	spin_unlock(&mmg->lock);
	if (rc < 0) {
		dev_err(mmg->dev,
			"%s: Failed to allocate IDR for a new buffer, rc=%d\n",
			behavior->topic, rc);
		goto free_buf;
	}

	buf->mmg = mmg;
	buf->behavior = behavior;
	buf->handle = (((u64)rc | buf->behavior->mem_id) << PAGE_SHIFT);
	kref_init(&buf->refcount);

	rc = buf->behavior->alloc(buf, gfp, args);
	if (rc) {
		dev_err(mmg->dev, "%s: Failure in buffer alloc callback %d\n",
			behavior->topic, rc);
		goto remove_idr;
	}

	return buf;

remove_idr:
	spin_lock(&mmg->lock);
	idr_remove(&mmg->handles, lower_32_bits(buf->handle >> PAGE_SHIFT));
	spin_unlock(&mmg->lock);
free_buf:
	kfree(buf);
	return NULL;
}

/**
 * hl_mmap_mem_buf_vm_close - handle mmap close
 *
 * @vma: the vma object for which mmap was closed.
 *
 * Put the memory buffer if it is no longer mapped.
 */
static void hl_mmap_mem_buf_vm_close(struct vm_area_struct *vma)
{
	struct hl_mmap_mem_buf *buf =
		(struct hl_mmap_mem_buf *)vma->vm_private_data;
	long new_mmap_size;

	new_mmap_size = buf->real_mapped_size - (vma->vm_end - vma->vm_start);

	if (new_mmap_size > 0) {
		buf->real_mapped_size = new_mmap_size;
		return;
	}

	atomic_set(&buf->mmap, 0);
	hl_mmap_mem_buf_put(buf);
	vma->vm_private_data = NULL;
}

static const struct vm_operations_struct hl_mmap_mem_buf_vm_ops = {
	.close = hl_mmap_mem_buf_vm_close
};

/**
 * hl_mem_mgr_mmap - map the given buffer to the user
 *
 * @mmg: unifed memory manager
 * @vma: the vma object for which mmap was closed.
 * @args: additional args passed to behavior->mmap
 *
 * Map the buffer specified by the vma->vm_pgoff to the given vma.
 */
int hl_mem_mgr_mmap(struct hl_mem_mgr *mmg, struct vm_area_struct *vma,
		    void *args)
{
	struct hl_mmap_mem_buf *buf;
	u64 user_mem_size;
	u64 handle;
	int rc;

	/* We use the page offset to hold the idr and thus we need to clear
	 * it before doing the mmap itself
	 */
	handle = vma->vm_pgoff << PAGE_SHIFT;
	vma->vm_pgoff = 0;

	/* Reference was taken here */
	buf = hl_mmap_mem_buf_get(mmg, handle);
	if (!buf) {
		dev_err(mmg->dev,
			"Memory mmap failed, no match to handle %#llx\n", handle);
		return -EINVAL;
	}

	/* Validation check */
	user_mem_size = vma->vm_end - vma->vm_start;
	if (user_mem_size != ALIGN(buf->mappable_size, PAGE_SIZE)) {
		dev_err(mmg->dev,
			"%s: Memory mmap failed, mmap VM size 0x%llx != 0x%llx allocated physical mem size\n",
			buf->behavior->topic, user_mem_size, buf->mappable_size);
		rc = -EINVAL;
		goto put_mem;
	}

#ifdef _HAS_TYPE_ARG_IN_ACCESS_OK
	if (!access_ok(VERIFY_WRITE, (void __user *)(uintptr_t)vma->vm_start,
		       user_mem_size)) {
#else
	if (!access_ok((void __user *)(uintptr_t)vma->vm_start,
		       user_mem_size)) {
#endif
		dev_err(mmg->dev, "%s: User pointer is invalid - 0x%lx\n",
			buf->behavior->topic, vma->vm_start);

		rc = -EINVAL;
		goto put_mem;
	}

	if (atomic_cmpxchg(&buf->mmap, 0, 1)) {
		dev_err(mmg->dev,
			"%s, Memory mmap failed, already mmaped to user\n",
			buf->behavior->topic);
		rc = -EINVAL;
		goto put_mem;
	}

	vma->vm_ops = &hl_mmap_mem_buf_vm_ops;

	/* Note: We're transferring the memory reference to vma->vm_private_data here. */

	vma->vm_private_data = buf;

	rc = buf->behavior->mmap(buf, vma, args);
	if (rc) {
		atomic_set(&buf->mmap, 0);
		goto put_mem;
	}

	buf->real_mapped_size = buf->mappable_size;
	vma->vm_pgoff = handle >> PAGE_SHIFT;

	return 0;

put_mem:
	hl_mmap_mem_buf_put(buf);
	return rc;
}

/**
 * hl_mem_mgr_init - initialize unified memory manager
 *
 * @dev: owner device pointer
 * @mmg: structure to initialize
 *
 * Initialize an instance of unified memory manager
 */
void hl_mem_mgr_init(struct device *dev, struct hl_mem_mgr *mmg)
{
	mmg->dev = dev;
	spin_lock_init(&mmg->lock);
	idr_init(&mmg->handles);
}

/**
 * hl_mem_mgr_fini - release unified memory manager
 *
 * @mmg: parent unifed memory manager
 *
 * Release the unified memory manager. Shall be called from an interrupt context.
 */
void hl_mem_mgr_fini(struct hl_mem_mgr *mmg)
{
	struct hl_mmap_mem_buf *buf;
	struct idr *idp;
	const char *topic;
	u32 id;

	idp = &mmg->handles;

	idr_for_each_entry(idp, buf, id) {
		topic = buf->behavior->topic;
		if (hl_mmap_mem_buf_put(buf) != 1)
			dev_err(mmg->dev,
				"%s: Buff handle %u for CTX is still alive\n",
				topic, id);
	}

	/* TODO: can it happen that some buffer is still in use at this point? */

	idr_destroy(&mmg->handles);
}