xref: /haiku/src/system/boot/loader/kernel_args.cpp (revision d3d8b26997fac34a84981e6d2b649521de2cc45a)
1 /*
2 ** Copyright 2004, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3 ** Distributed under the terms of the Haiku License.
4 */
5 
6 
7 #include <OS.h>
8 
9 #include <kernel.h>
10 #include <boot/stage2.h>
11 #include <boot/platform.h>
12 
13 #include <string.h>
14 
15 
16 static const size_t kChunkSize = 16384;
17 
18 kernel_args gKernelArgs;
19 
20 static void *sFirstFree;
21 static void *sLast;
22 static size_t sFree = kChunkSize;
23 
24 
25 static void
26 remove_range_index(addr_range *ranges, uint32 &numRanges, uint32 index)
27 {
28 	if (index + 1 == numRanges) {
29 		// remove last range
30 		numRanges--;
31 		return;
32 	}
33 
34 	memmove(&ranges[index], &ranges[index + 1], sizeof(addr_range) * (numRanges - 1 - index));
35 	numRanges--;
36 }
37 
38 
39 /**	Inserts the specified (start, size) pair (aka range) in the
40  *	addr_range array.
41  *	It will extend existing ranges in order to have as little
42  *	ranges in the array as possible.
43  *	Returns B_OK on success, or B_ENTRY_NOT_FOUND if there was
44  *	no free array entry available anymore.
45  */
46 
47 extern "C" status_t
48 insert_address_range(addr_range *ranges, uint32 *_numRanges, uint32 maxRanges,
49 	addr_t start, uint32 size)
50 {
51 	uint32 numRanges = *_numRanges;
52 
53 	start = ROUNDOWN(start, B_PAGE_SIZE);
54 	size = ROUNDUP(size, B_PAGE_SIZE);
55 	addr_t end = start + size;
56 
57 	for (uint32 i = 0; i < numRanges; i++) {
58 		addr_t rangeStart = ranges[i].start;
59 		addr_t rangeEnd = rangeStart + ranges[i].size;
60 
61 		if (end < rangeStart || start > rangeEnd) {
62 			// ranges don't intersect or touch each other
63 			continue;
64 		}
65 		if (start >= rangeStart && end <= rangeEnd) {
66 			// range is already completely covered
67 			return B_OK;
68 		}
69 
70 		if (start < rangeStart) {
71 			// prepend to the existing range
72 			ranges[i].start = start;
73 			ranges[i].size += rangeStart - start;
74 		}
75 		if (end > ranges[i].start + ranges[i].size) {
76 			// append to the existing range
77 			ranges[i].size = end - ranges[i].start;
78 		}
79 
80 		// join ranges if possible
81 
82 		for (uint32 j = 0; j < numRanges; j++) {
83 			if (i == j)
84 				continue;
85 
86 			rangeStart = ranges[i].start;
87 			rangeEnd = rangeStart + ranges[i].size;
88 			addr_t joinStart = ranges[j].start;
89 			addr_t joinEnd = joinStart + ranges[j].size;
90 
91 			if (rangeStart <= joinEnd && joinEnd <= rangeEnd) {
92 				// join range that used to be before the current one, or
93 				// the one that's now entirely included by the current one
94 				if (joinStart < rangeStart) {
95 					ranges[i].size += rangeStart - joinStart;
96 					ranges[i].start = joinStart;
97 				}
98 
99 				remove_range_index(ranges, numRanges, j--);
100 			} else if (joinStart <= rangeEnd && joinEnd > rangeEnd) {
101 				// join range that used to be after the current one
102 				ranges[i].size += joinEnd - rangeEnd;
103 
104 				remove_range_index(ranges, numRanges, j--);
105 			}
106 		}
107 
108 		*_numRanges = numRanges;
109 		return B_OK;
110 	}
111 
112 	// no range matched, we need to create a new one
113 
114 	if (numRanges >= maxRanges)
115 		return B_ENTRY_NOT_FOUND;
116 
117 	ranges[numRanges].start = (addr_t)start;
118 	ranges[numRanges].size = size;
119 	(*_numRanges)++;
120 
121 	return B_OK;
122 }
123 
124 
125 static status_t
126 add_kernel_args_range(void *start, uint32 size)
127 {
128 	return insert_address_range(gKernelArgs.kernel_args_range,
129 				&gKernelArgs.num_kernel_args_ranges, MAX_KERNEL_ARGS_RANGE,
130 				(addr_t)start, size);
131 }
132 
133 
134 /** This function can be used to allocate memory that is going
135  *	to be passed over to the kernel. For example, the preloaded_image
136  *	structures are allocated this way.
137  *	The boot loader heap doesn't make it into the kernel!
138  */
139 
140 extern "C" void *
141 kernel_args_malloc(size_t size)
142 {
143 	if (sFirstFree != NULL && size <= sFree) {
144 		// there is enough space in the current buffer
145 		void *address = sFirstFree;
146 		sFirstFree = (void *)((addr_t)sFirstFree + size);
147 		sLast = address;
148 		sFree -= size;
149 
150 		return address;
151 	}
152 
153 	if (size > kChunkSize / 2 && sFree < size) {
154 		// the block is so large, we'll allocate a new block for it
155 		void *block = NULL;
156 		if (platform_allocate_region(&block, size, B_READ_AREA | B_WRITE_AREA,
157 			false) != B_OK) {
158 			return NULL;
159 		}
160 
161 		if (add_kernel_args_range(block, size) != B_OK)
162 			panic("kernel_args max range to low!\n");
163 		return block;
164 	}
165 
166 	// just allocate a new block and "close" the old one
167 	void *block = NULL;
168 	if (platform_allocate_region(&block, kChunkSize,
169 		B_READ_AREA | B_WRITE_AREA, false) != B_OK) {
170 		return NULL;
171 	}
172 
173 	sFirstFree = (void *)((addr_t)block + size);
174 	sLast = block;
175 	sFree = kChunkSize - size;
176 	if (add_kernel_args_range(block, kChunkSize) != B_OK)
177 		panic("kernel_args max range to low!\n");
178 
179 	return block;
180 }
181 
182 
183 /** Convenience function that copies strdup() functions for the
184  *	kernel args heap.
185  */
186 
187 extern "C" char *
188 kernel_args_strdup(const char *string)
189 {
190 	if (string == NULL || string[0] == '\0')
191 		return NULL;
192 
193 	size_t length = strlen(string) + 1;
194 
195 	char *target = (char *)kernel_args_malloc(length);
196 	if (target == NULL)
197 		return NULL;
198 
199 	memcpy(target, string, length);
200 	return target;
201 }
202 
203 
204 /** This function frees a block allocated via kernel_args_malloc().
205  *	It's very simple; it can only free the last allocation. It's
206  *	enough for its current usage in the boot loader, though.
207  */
208 
209 extern "C" void
210 kernel_args_free(void *block)
211 {
212 	if (sLast != block) {
213 		// sorry, we're dumb
214 		return;
215 	}
216 
217 	sFree = (addr_t)sFirstFree - (addr_t)sLast;
218 	sFirstFree = block;
219 	sLast = NULL;
220 }
221 
222