aboutsummaryrefslogtreecommitdiff
path: root/drivers/staging/dt3155/allocator.c
blob: db382ef90217727f9c0012528a7c223f8c5c83d4 (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
/*
 * allocator.c -- allocate after high_memory, if available
 *
 * NOTE: this is different from my previous allocator, the one that
 *       assembles pages, which revealed itself both slow and unreliable.
 *
 * Copyright (C) 1998   rubini@linux.it (Alessandro Rubini)
 *
 *   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 of the License, 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.
 *

-- Changes --

  Date	      Programmer  Description of changes made
  -------------------------------------------------------------------
  02-Aug-2002 NJC         allocator now steps in 1MB increments, rather
			  than doubling its size each time.
			  Also, allocator_init(u32 *) now returns
			  (in the first arg) the size of the free
			  space.  This is no longer consistent with
			  using the allocator as a module, and some changes
			  may be necessary for that purpose.  This was
			  designed to work with the DT3155 driver, in
			  stand alone mode only!!!
  26-Oct-2009 SS	  Port to 2.6.30 kernel.
 */


#ifndef __KERNEL__
#  define __KERNEL__
#endif
#ifndef MODULE
#  define MODULE
#endif

#include <linux/version.h>

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/mm.h>	/* PAGE_ALIGN() */
#include <linux/io.h>
#include <linux/slab.h>

#include <asm/page.h>

/*#define ALL_DEBUG*/
#define ALL_MSG "allocator: "

#undef PDEBUG             /* undef it, just in case */
#ifdef ALL_DEBUG
#  define __static
#  define DUMP_LIST() dump_list()
#  ifdef __KERNEL__
     /* This one if debugging is on, and kernel space */
#    define PDEBUG(fmt, args...) printk(KERN_DEBUG ALL_MSG fmt, ## args)
#  else
     /* This one for user space */
#    define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
#  endif
#else
#  define PDEBUG(fmt, args...) /* not debugging: nothing */
#  define DUMP_LIST()
#  define __static static
#endif

#undef PDEBUGG
#define PDEBUGG(fmt, args...)
/*#define PDEBUGG(fmt, args...) printk( KERN_DEBUG ALL_MSG fmt, ## args)*/


int allocator_himem = 1; /* 0 = probe, pos. = megs, neg. = disable   */
int allocator_step = 1;  /* This is the step size in MB              */
int allocator_probe = 1; /* This is a flag -- 1=probe, 0=don't probe */

static unsigned long allocator_buffer;		/* physical address */
static unsigned long allocator_buffer_size;	/* kilobytes */

/*
 * The allocator keeps a list of DMA areas, so multiple devices
 * can coexist. The list is kept sorted by address
 */

struct allocator_struct {
	unsigned long address;
	unsigned long size;
	struct allocator_struct *next;
};

struct allocator_struct *allocator_list;


#ifdef ALL_DEBUG
static int dump_list(void)
{
	struct allocator_struct *ptr;

	PDEBUG("Current list:\n");
	for (ptr = allocator_list; ptr; ptr = ptr->next)
		PDEBUG("0x%08lx (size %likB)\n", ptr->address, ptr->size>>10);
	return 0;
}
#endif

/* ========================================================================
 * This function is the actual allocator.
 *
 * If space is available in high memory (as detected at load time), that
 * one is returned. The return value is a physical address (i.e., it can
 * be used straight ahead for DMA, but needs remapping for program use).
 */

unsigned long allocator_allocate_dma(unsigned long kilobytes, int prio)
{
	struct allocator_struct *ptr = allocator_list, *newptr;
	unsigned long bytes = kilobytes << 10;

	/* check if high memory is available */
	if (!allocator_buffer)
		return 0;

	/* Round it to a multiple of the pagesize */
	bytes = PAGE_ALIGN(bytes);
	PDEBUG("request for %li bytes\n", bytes);

	while (ptr && ptr->next) {
		if (ptr->next->address - (ptr->address + ptr->size) >= bytes)
			break; /* enough space */
		ptr = ptr->next;
	}
	if (!ptr->next) {
		DUMP_LIST();
		PDEBUG("alloc failed\n");
		return 0; /* end of list */
	}
	newptr = kmalloc(sizeof(struct allocator_struct), prio);
	if (!newptr)
		return 0;

	/* ok, now stick it after ptr */
	newptr->address = ptr->address + ptr->size;
	newptr->size = bytes;
	newptr->next = ptr->next;
	ptr->next = newptr;

	DUMP_LIST();
	PDEBUG("returning 0x%08lx\n", newptr->address);
	return newptr->address;
}

int allocator_free_dma(unsigned long address)
{
	struct allocator_struct *ptr = allocator_list, *prev;

	while (ptr && ptr->next) {
		if (ptr->next->address == address)
			break;
		ptr = ptr->next;
	}
	/* the one being freed is ptr->next */
	prev = ptr; ptr = ptr->next;

	if (!ptr) {
		printk(KERN_ERR ALL_MSG
			"free_dma(0x%08lx) but add. not allocated\n",
			ptr->address);
		return -EINVAL;
	}
	PDEBUGG("freeing: %08lx (%li) next %08lx\n", ptr->address, ptr->size,
		ptr->next->address);
	prev->next = ptr->next;
	kfree(ptr);

	/* dump_list(); */
	return 0;
}

/* ========================================================================
 * Init and cleanup
 *
 * On cleanup everything is released. If the list is not empty, that a
 * problem of our clients
 */
int allocator_init(u32 *allocator_max)
{
	/* check how much free memory is there */
	void *remapped;
	unsigned long max;
	unsigned long trial_size = allocator_himem<<20;
	unsigned long last_trial = 0;
	unsigned long step = allocator_step<<20;
	unsigned long i = 0;
	struct allocator_struct *head, *tail;
	char test_string[] = "0123456789abcde"; /* 16 bytes */

	PDEBUGG("himem = %i\n", allocator_himem);
	if (allocator_himem < 0) /* don't even try */
		return -EINVAL;

	if (!trial_size)
		trial_size = 1<<20; /* not specified: try one meg */

	while (1) {
		remapped = ioremap(__pa(high_memory), trial_size);
		if (!remapped) {
			PDEBUGG("%li megs failed!\n", trial_size>>20);
			break;
		}
		PDEBUGG("Trying %li megs (at %p, %p)\n", trial_size>>20,
			(void *)__pa(high_memory), remapped);
		for (i = last_trial; i < trial_size; i += 16) {
			strcpy((char *)(remapped)+i, test_string);
			if (strcmp((char *)(remapped)+i, test_string))
				break;
			}
		iounmap((void *)remapped);
		schedule();
		last_trial = trial_size;
		if (i == trial_size)
			trial_size += step; /* increment, if all went well */
		else {
			PDEBUGG("%li megs copy test failed!\n", trial_size>>20);
			break;
		}
		if (!allocator_probe)
			break;
	}
	PDEBUG("%li megs (%li k, %li b)\n", i>>20, i>>10, i);
	allocator_buffer_size = i>>10; /* kilobytes */
	allocator_buffer = __pa(high_memory);
	if (!allocator_buffer_size) {
		printk(KERN_WARNING ALL_MSG "no free high memory to use\n");
		return -ENOMEM;
	}

	/*
	* to simplify things, always have two cells in the list:
	* the first and the last. This avoids some conditionals and
	* extra code when allocating and deallocating: we only play
	* in the middle of the list
	*/
	head = kmalloc(sizeof(struct allocator_struct), GFP_KERNEL);
	if (!head)
		return -ENOMEM;
	tail = kmalloc(sizeof(struct allocator_struct), GFP_KERNEL);
	if (!tail) {
		kfree(head);
		return -ENOMEM;
	}

	max = allocator_buffer_size<<10;

	head->size = tail->size = 0;
	head->address = allocator_buffer;
	tail->address = allocator_buffer + max;
	head->next = tail;
	tail->next = NULL;
	allocator_list = head;

	/* Back to the user code, in KB */
	*allocator_max = allocator_buffer_size;

	return 0; /* ok, ready */
}

void allocator_cleanup(void)
{
	struct allocator_struct *ptr, *next;

	for (ptr = allocator_list; ptr; ptr = next) {
		next = ptr->next;
		PDEBUG("freeing list: 0x%08lx\n", ptr->address);
		kfree(ptr);
	}

	allocator_buffer      = 0;
	allocator_buffer_size = 0;
	allocator_list = NULL;
}