aboutsummaryrefslogtreecommitdiff
path: root/drivers/staging/tidspbridge/core/dsp-mmu.c
blob: 983c95adc8ff2b84f51602da4324059c7b5345f5 (plain)
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
/*
 * dsp-mmu.c
 *
 * DSP-BIOS Bridge driver support functions for TI OMAP processors.
 *
 * DSP iommu.
 *
 * Copyright (C) 2010 Texas Instruments, Inc.
 *
 * This package is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include <dspbridge/host_os.h>
#include <plat/dmtimer.h>
#include <dspbridge/dbdefs.h>
#include <dspbridge/dev.h>
#include <dspbridge/io_sm.h>
#include <dspbridge/dspdeh.h>
#include "_tiomap.h"

#include <dspbridge/dsp-mmu.h>

#define MMU_CNTL_TWL_EN		(1 << 2)

static struct tasklet_struct mmu_tasklet;

#ifdef CONFIG_TIDSPBRIDGE_BACKTRACE
static void mmu_fault_print_stack(struct bridge_dev_context *dev_context)
{
	void *dummy_addr;
	u32 fa, tmp;
	struct iotlb_entry e;
	struct iommu *mmu = dev_context->dsp_mmu;
	dummy_addr = (void *)__get_free_page(GFP_ATOMIC);

	/*
	 * Before acking the MMU fault, let's make sure MMU can only
	 * access entry #0. Then add a new entry so that the DSP OS
	 * can continue in order to dump the stack.
	 */
	tmp = iommu_read_reg(mmu, MMU_CNTL);
	tmp &= ~MMU_CNTL_TWL_EN;
	iommu_write_reg(mmu, tmp, MMU_CNTL);
	fa = iommu_read_reg(mmu, MMU_FAULT_AD);
	e.da = fa & PAGE_MASK;
	e.pa = virt_to_phys(dummy_addr);
	e.valid = 1;
	e.prsvd = 1;
	e.pgsz = IOVMF_PGSZ_4K & MMU_CAM_PGSZ_MASK;
	e.endian = MMU_RAM_ENDIAN_LITTLE;
	e.elsz = MMU_RAM_ELSZ_32;
	e.mixed = 0;

	load_iotlb_entry(mmu, &e);

	dsp_clk_enable(DSP_CLK_GPT8);

	dsp_gpt_wait_overflow(DSP_CLK_GPT8, 0xfffffffe);

	/* Clear MMU interrupt */
	tmp = iommu_read_reg(mmu, MMU_IRQSTATUS);
	iommu_write_reg(mmu, tmp, MMU_IRQSTATUS);

	dump_dsp_stack(dev_context);
	dsp_clk_disable(DSP_CLK_GPT8);

	iopgtable_clear_entry(mmu, fa);
	free_page((unsigned long)dummy_addr);
}
#endif


static void fault_tasklet(unsigned long data)
{
	struct iommu *mmu = (struct iommu *)data;
	struct bridge_dev_context *dev_ctx;
	struct deh_mgr *dm;
	u32 fa;
	dev_get_deh_mgr(dev_get_first(), &dm);
	dev_get_bridge_context(dev_get_first(), &dev_ctx);

	if (!dm || !dev_ctx)
		return;

	fa = iommu_read_reg(mmu, MMU_FAULT_AD);

#ifdef CONFIG_TIDSPBRIDGE_BACKTRACE
	print_dsp_trace_buffer(dev_ctx);
	dump_dl_modules(dev_ctx);
	mmu_fault_print_stack(dev_ctx);
#endif

	bridge_deh_notify(dm, DSP_MMUFAULT, fa);
}

/*
 *  ======== mmu_fault_isr ========
 *      ISR to be triggered by a DSP MMU fault interrupt.
 */
static int mmu_fault_callback(struct iommu *mmu)
{
	if (!mmu)
		return -EPERM;

	iommu_write_reg(mmu, 0, MMU_IRQENABLE);
	tasklet_schedule(&mmu_tasklet);
	return 0;
}

/**
 * dsp_mmu_init() - initialize dsp_mmu module and returns a handle
 *
 * This function initialize dsp mmu module and returns a struct iommu
 * handle to use it for dsp maps.
 *
 */
struct iommu *dsp_mmu_init()
{
	struct iommu *mmu;

	mmu = iommu_get("iva2");

	if (!IS_ERR(mmu)) {
		tasklet_init(&mmu_tasklet, fault_tasklet, (unsigned long)mmu);
		mmu->isr = mmu_fault_callback;
	}

	return mmu;
}

/**
 * dsp_mmu_exit() - destroy dsp mmu module
 * @mmu:	Pointer to iommu handle.
 *
 * This function destroys dsp mmu module.
 *
 */
void dsp_mmu_exit(struct iommu *mmu)
{
	if (mmu)
		iommu_put(mmu);
	tasklet_kill(&mmu_tasklet);
}

/**
 * user_va2_pa() - get physical address from userspace address.
 * @mm:		mm_struct Pointer of the process.
 * @address:	Virtual user space address.
 *
 */
static u32 user_va2_pa(struct mm_struct *mm, u32 address)
{
	pgd_t *pgd;
	pmd_t *pmd;
	pte_t *ptep, pte;

	pgd = pgd_offset(mm, address);
	if (!(pgd_none(*pgd) || pgd_bad(*pgd))) {
		pmd = pmd_offset(pgd, address);
		if (!(pmd_none(*pmd) || pmd_bad(*pmd))) {
			ptep = pte_offset_map(pmd, address);
			if (ptep) {
				pte = *ptep;
				if (pte_present(pte))
					return pte & PAGE_MASK;
			}
		}
	}

	return 0;
}

/**
 * get_io_pages() - pin and get pages of io user's buffer.
 * @mm:		mm_struct Pointer of the process.
 * @uva:		Virtual user space address.
 * @pages	Pages to be pined.
 * @usr_pgs	struct page array pointer where the user pages will be stored
 *
 */
static int get_io_pages(struct mm_struct *mm, u32 uva, unsigned pages,
						struct page **usr_pgs)
{
	u32 pa;
	int i;
	struct page *pg;

	for (i = 0; i < pages; i++) {
		pa = user_va2_pa(mm, uva);

		if (!pfn_valid(__phys_to_pfn(pa)))
			break;

		pg = phys_to_page(pa);
		usr_pgs[i] = pg;
		get_page(pg);
	}
	return i;
}

/**
 * user_to_dsp_map() - maps user to dsp virtual address
 * @mmu:	Pointer to iommu handle.
 * @uva:		Virtual user space address.
 * @da		DSP address
 * @size		Buffer size to map.
 * @usr_pgs	struct page array pointer where the user pages will be stored
 *
 * This function maps a user space buffer into DSP virtual address.
 *
 */
u32 user_to_dsp_map(struct iommu *mmu, u32 uva, u32 da, u32 size,
				struct page **usr_pgs)
{
	int res, w;
	unsigned pages;
	int i;
	struct vm_area_struct *vma;
	struct mm_struct *mm = current->mm;
	struct sg_table *sgt;
	struct scatterlist *sg;

	if (!size || !usr_pgs)
		return -EINVAL;

	pages = size / PG_SIZE4K;

	down_read(&mm->mmap_sem);
	vma = find_vma(mm, uva);
	while (vma && (uva + size > vma->vm_end))
		vma = find_vma(mm, vma->vm_end + 1);

	if (!vma) {
		pr_err("%s: Failed to get VMA region for 0x%x (%d)\n",
						__func__, uva, size);
		up_read(&mm->mmap_sem);
		return -EINVAL;
	}
	if (vma->vm_flags & (VM_WRITE | VM_MAYWRITE))
		w = 1;

	if (vma->vm_flags & VM_IO)
		i = get_io_pages(mm, uva, pages, usr_pgs);
	else
		i = get_user_pages(current, mm, uva, pages, w, 1,
							usr_pgs, NULL);
	up_read(&mm->mmap_sem);

	if (i < 0)
		return i;

	if (i < pages) {
		res = -EFAULT;
		goto err_pages;
	}

	sgt = kzalloc(sizeof(*sgt), GFP_KERNEL);
	if (!sgt) {
		res = -ENOMEM;
		goto err_pages;
	}

	res = sg_alloc_table(sgt, pages, GFP_KERNEL);

	if (res < 0)
		goto err_sg;

	for_each_sg(sgt->sgl, sg, sgt->nents, i)
		sg_set_page(sg, usr_pgs[i], PAGE_SIZE, 0);

	da = iommu_vmap(mmu, da, sgt, IOVMF_ENDIAN_LITTLE | IOVMF_ELSZ_32);

	if (!IS_ERR_VALUE(da))
		return da;
	res = (int)da;

	sg_free_table(sgt);
err_sg:
	kfree(sgt);
	i = pages;
err_pages:
	while (i--)
		put_page(usr_pgs[i]);
	return res;
}

/**
 * user_to_dsp_unmap() - unmaps DSP virtual buffer.
 * @mmu:	Pointer to iommu handle.
 * @da		DSP address
 *
 * This function unmaps a user space buffer into DSP virtual address.
 *
 */
int user_to_dsp_unmap(struct iommu *mmu, u32 da)
{
	unsigned i;
	struct sg_table *sgt;
	struct scatterlist *sg;

	sgt = iommu_vunmap(mmu, da);
	if (!sgt)
		return -EFAULT;

	for_each_sg(sgt->sgl, sg, sgt->nents, i)
		put_page(sg_page(sg));
	sg_free_table(sgt);
	kfree(sgt);

	return 0;
}