summaryrefslogtreecommitdiffstats
path: root/arch/powerpc/platforms/cell/spufs/lscsa_alloc.c
blob: f4b3c052dabf599888fcb5661198f4139245f15f (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
/*
 * SPU local store allocation routines
 *
 * Copyright 2007 Benjamin Herrenschmidt, IBM Corp.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#undef DEBUG

#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>

#include <asm/spu.h>
#include <asm/spu_csa.h>
#include <asm/mmu.h>

static int spu_alloc_lscsa_std(struct spu_state *csa)
{
	struct spu_lscsa *lscsa;
	unsigned char *p;

	lscsa = vmalloc(sizeof(struct spu_lscsa));
	if (!lscsa)
		return -ENOMEM;
	memset(lscsa, 0, sizeof(struct spu_lscsa));
	csa->lscsa = lscsa;

	/* Set LS pages reserved to allow for user-space mapping. */
	for (p = lscsa->ls; p < lscsa->ls + LS_SIZE; p += PAGE_SIZE)
		SetPageReserved(vmalloc_to_page(p));

	return 0;
}

static void spu_free_lscsa_std(struct spu_state *csa)
{
	/* Clear reserved bit before vfree. */
	unsigned char *p;

	if (csa->lscsa == NULL)
		return;

	for (p = csa->lscsa->ls; p < csa->lscsa->ls + LS_SIZE; p += PAGE_SIZE)
		ClearPageReserved(vmalloc_to_page(p));

	vfree(csa->lscsa);
}

#ifdef CONFIG_SPU_FS_64K_LS

#define SPU_64K_PAGE_SHIFT	16
#define SPU_64K_PAGE_ORDER	(SPU_64K_PAGE_SHIFT - PAGE_SHIFT)
#define SPU_64K_PAGE_COUNT	(1ul << SPU_64K_PAGE_ORDER)

int spu_alloc_lscsa(struct spu_state *csa)
{
	struct page	**pgarray;
	unsigned char	*p;
	int		i, j, n_4k;

	/* Check availability of 64K pages */
	if (mmu_psize_defs[MMU_PAGE_64K].shift == 0)
		goto fail;

	csa->use_big_pages = 1;

	pr_debug("spu_alloc_lscsa(csa=0x%p), trying to allocate 64K pages\n",
		 csa);

	/* First try to allocate our 64K pages. We need 5 of them
	 * with the current implementation. In the future, we should try
	 * to separate the lscsa with the actual local store image, thus
	 * allowing us to require only 4 64K pages per context
	 */
	for (i = 0; i < SPU_LSCSA_NUM_BIG_PAGES; i++) {
		/* XXX This is likely to fail, we should use a special pool
		 *     similiar to what hugetlbfs does.
		 */
		csa->lscsa_pages[i] = alloc_pages(GFP_KERNEL,
						  SPU_64K_PAGE_ORDER);
		if (csa->lscsa_pages[i] == NULL)
			goto fail;
	}

	pr_debug(" success ! creating vmap...\n");

	/* Now we need to create a vmalloc mapping of these for the kernel
	 * and SPU context switch code to use. Currently, we stick to a
	 * normal kernel vmalloc mapping, which in our case will be 4K
	 */
	n_4k = SPU_64K_PAGE_COUNT * SPU_LSCSA_NUM_BIG_PAGES;
	pgarray = kmalloc(sizeof(struct page *) * n_4k, GFP_KERNEL);
	if (pgarray == NULL)
		goto fail;
	for (i = 0; i < SPU_LSCSA_NUM_BIG_PAGES; i++)
		for (j = 0; j < SPU_64K_PAGE_COUNT; j++)
			/* We assume all the struct page's are contiguous
			 * which should be hopefully the case for an order 4
			 * allocation..
			 */
			pgarray[i * SPU_64K_PAGE_COUNT + j] =
				csa->lscsa_pages[i] + j;
	csa->lscsa = vmap(pgarray, n_4k, VM_USERMAP, PAGE_KERNEL);
	kfree(pgarray);
	if (csa->lscsa == NULL)
		goto fail;

	memset(csa->lscsa, 0, sizeof(struct spu_lscsa));

	/* Set LS pages reserved to allow for user-space mapping.
	 *
	 * XXX isn't that a bit obsolete ? I think we should just
	 * make sure the page count is high enough. Anyway, won't harm
	 * for now
	 */
	for (p = csa->lscsa->ls; p < csa->lscsa->ls + LS_SIZE; p += PAGE_SIZE)
		SetPageReserved(vmalloc_to_page(p));

	pr_debug(" all good !\n");

	return 0;
fail:
	pr_debug("spufs: failed to allocate lscsa 64K pages, falling back\n");
	spu_free_lscsa(csa);
	return spu_alloc_lscsa_std(csa);
}

void spu_free_lscsa(struct spu_state *csa)
{
	unsigned char *p;
	int i;

	if (!csa->use_big_pages) {
		spu_free_lscsa_std(csa);
		return;
	}
	csa->use_big_pages = 0;

	if (csa->lscsa == NULL)
		goto free_pages;

	for (p = csa->lscsa->ls; p < csa->lscsa->ls + LS_SIZE; p += PAGE_SIZE)
		ClearPageReserved(vmalloc_to_page(p));

	vunmap(csa->lscsa);
	csa->lscsa = NULL;

 free_pages:

	for (i = 0; i < SPU_LSCSA_NUM_BIG_PAGES; i++)
		if (csa->lscsa_pages[i])
			__free_pages(csa->lscsa_pages[i], SPU_64K_PAGE_ORDER);
}

#else /* CONFIG_SPU_FS_64K_LS */

int spu_alloc_lscsa(struct spu_state *csa)
{
	return spu_alloc_lscsa_std(csa);
}

void spu_free_lscsa(struct spu_state *csa)
{
	spu_free_lscsa_std(csa);
}

#endif /* !defined(CONFIG_SPU_FS_64K_LS) */