aboutsummaryrefslogtreecommitdiff
path: root/drivers/soc/qcom/ocmem_allocator.c
blob: aa4e91365432281fa178fb980364780dab7b1ec3 (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
/* Copyright (c) 2012,2014, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * 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.
 */

#include <soc/qcom/ocmem.h>
#include "ocmem_priv.h"
#include <linux/genalloc.h>

/* All allocator operations are serialized by ocmem driver */

/* The allocators work as follows:
	Constraints:
	1) There is no IOMMU access to OCMEM hence successive allocations
		in the zone must be physically contiguous
	2) Allocations must be freed in reverse order within a zone.

	z->z_start: Fixed pointer to the start of a zone
	z->z_end:   Fixed pointer to the end of a zone

	z->z_head:  Movable pointer to the next free area when growing at head
			Fixed on zones that grow from tail

	z->z_tail:  Movable pointer to the next free area when growing at tail
			Fixed on zones that grow from head

	z->z_free:  Free space in a zone that is updated on an allocation/free

	reserve:    Enable libgenpool to simulate tail allocations
*/

int allocate_head(struct ocmem_zone *z, unsigned long size,
							unsigned long *offset)
{
	*offset  = gen_pool_alloc(z->z_pool, size);

	if (!(*offset))
		return -ENOMEM;

	z->z_head += size;
	z->z_free -= size;
	return 0;
}

int allocate_tail(struct ocmem_zone *z, unsigned long size,
							unsigned long *offset)
{
	unsigned long reserve;
	unsigned long head;

	if (z->z_tail < (z->z_head + size))
		return -ENOMEM;

	reserve = z->z_tail - z->z_head - size;
	if (reserve) {
		head = gen_pool_alloc(z->z_pool, reserve);
		*offset = gen_pool_alloc(z->z_pool, size);
		gen_pool_free(z->z_pool, head, reserve);
	} else
		*offset = gen_pool_alloc(z->z_pool, size);

	if (!(*offset))
		return -ENOMEM;

	z->z_tail -= size;
	z->z_free -= size;
	return 0;
}

int free_head(struct ocmem_zone *z, unsigned long offset,
			unsigned long size)
{
	if (offset > z->z_head) {
		pr_err("ocmem: Detected out of order free "
				"leading to fragmentation\n");
		return -EINVAL;
	}
	gen_pool_free(z->z_pool, offset, size);
	z->z_head -= size;
	z->z_free += size;
	return 0;
}

int free_tail(struct ocmem_zone *z, unsigned long offset,
				unsigned long size)
{
	if (offset > z->z_tail) {
		pr_err("ocmem: Detected out of order free "
				"leading to fragmentation\n");
		return -EINVAL;
	}
	gen_pool_free(z->z_pool, offset, size);
	z->z_tail += size;
	z->z_free += size;
	return 0;
}