Initial revision
[reactos.git] / reactos / ntoskrnl / mm / virtual.c
1 /*
2 * COPYRIGHT: See COPYING in the top directory
3 * PROJECT: ReactOS kernel v0.0.2
4 * FILE: mm/virtual.cc
5 * PURPOSE: implementing the Virtualxxx section of the win32 api
6 * PROGRAMMER: David Welch
7 * UPDATE HISTORY:
8 * 09/4/98: Created
9 * 10/6/98: Corrections from Fatahi (i_fatahi@hotmail.com)
10 */
11
12 /* INCLUDE *****************************************************************/
13
14 #include <windows.h>
15
16 #include <internal/hal/segment.h>
17 #include <internal/mm.h>
18 #include <internal/hal/page.h>
19
20 #define NDEBUG
21 #include <internal/debug.h>
22
23 /* TYPES *******************************************************************/
24
25 /*
26 * These two are statically declared because mm is initalized before the
27 * memory pool
28 */
29 static memory_area kernel_area_desc;
30 static memory_area kernel_pool_desc;
31
32 /*
33 * Head of the list of system memory areas
34 */
35 memory_area* system_memory_area_list_head=&kernel_area_desc;
36
37 /*
38 * Head of the list of user memory areas (this should be per process)
39 */
40 memory_area* memory_area_list_head=NULL;
41
42 /*
43 * The base address of the kmalloc region
44 */
45 unsigned int kernel_pool_base = 0;
46
47
48 /* FUNCTIONS ****************************************************************/
49
50 void VirtualInit(boot_param* bp)
51 /*
52 * FUNCTION: Intialize the memory areas list
53 * ARGUMENTS:
54 * bp = Pointer to the boot parameters
55 * kernel_len = Length of the kernel
56 */
57 {
58 unsigned int kernel_len = bp->end_mem - bp->start_mem;
59
60 DPRINT("VirtualInit() %x\n",bp);
61
62 /*
63 * Setup the system area descriptor list
64 */
65 kernel_area_desc.base = KERNEL_BASE;
66 kernel_area_desc.length = kernel_len;
67 kernel_area_desc.next = &kernel_pool_desc;
68 kernel_area_desc.load_page=NULL;
69
70 /*
71 * The kmalloc area starts one page after the kernel area
72 */
73 kernel_pool_desc.base = KERNEL_BASE+ PAGE_ROUND_UP(kernel_len) + PAGESIZE;
74 kernel_pool_desc.length = NONPAGED_POOL_SIZE;
75 kernel_pool_desc.next = NULL;
76 kernel_pool_desc.load_page=NULL;
77
78 kernel_pool_base = kernel_pool_desc.base;
79 DPRINT("kmalloc_region_base %x\n",kernel_pool_base);
80 }
81
82
83 memory_area* find_first_marea(memory_area* list_head, unsigned int base,
84 unsigned int length)
85 /*
86 * FUNCTION: Returns the first memory area starting in the region or the last
87 * one before the start of the region
88 * ARGUMENTS:
89 * list_head = Head of the list of memory areas to search
90 * base = base address of the region
91 * length = length of the region
92 * RETURNS: A pointer to the area found or
93 * NULL if the region was before the first region on the list
94 */
95 {
96 memory_area* current=list_head;
97 for (;;)
98 {
99 if (current==NULL)
100 {
101 return(NULL);
102 }
103 if (current->base == base && length==0)
104 {
105 return(current);
106 }
107
108 if (current->base >= base)
109 {
110 if (current->base < (base+length))
111 {
112 return(current);
113 }
114 else
115 {
116 return(current->previous);
117 }
118 }
119 if (current->next==NULL)
120 {
121 return(current);
122 }
123 current=current->next;
124 }
125 return(NULL);
126 }
127
128 asmlinkage int page_fault_handler(unsigned int edi,
129 unsigned int esi, unsigned int ebp,
130 unsigned int esp, unsigned int ebx,
131 unsigned int edx, unsigned int ecx,
132 unsigned int eax,
133 unsigned int type,
134 unsigned int ds,
135 unsigned short int error_code,
136 unsigned int eip,
137 unsigned int cs, unsigned int eflags,
138 unsigned int esp0, unsigned int ss0)
139 /*
140 * FUNCTION: Handle a page fault
141 */
142 {
143 memory_area* marea=NULL;
144
145 /*
146 * Get the address for the page fault
147 */
148 unsigned int cr2;
149 __asm__("movl %%cr2,%0\n\t" : "=d" (cr2));
150 DPRINT("Page fault at address %x with eip %x\n",cr2,eip);
151
152 cr2 = PAGE_ROUND_DOWN(cr2);
153
154 /*
155 * Find the memory area for the faulting address
156 */
157 if (cr2>=KERNEL_BASE)
158 {
159 /*
160 * Check permissions
161 */
162 if (cs!=KERNEL_CS)
163 {
164 printk("%s:%d\n",__FILE__,__LINE__);
165 return(0);
166 }
167 marea=find_first_marea(system_memory_area_list_head,cr2,
168 PAGESIZE);
169 }
170 else
171 {
172 marea=find_first_marea(memory_area_list_head,cr2,
173 PAGESIZE);
174 }
175
176 /*
177 * If the access was to an invalid area of memory raise an exception
178 */
179 if (marea==NULL || marea->load_page==NULL)
180 {
181 printk("%s:%d\n",__FILE__,__LINE__);
182 return(0);
183 }
184
185 /*
186 * Check the access was within the area
187 */
188 if (cr2 > (marea->base + marea->length) || cr2 < marea->base)
189 {
190 DPRINT("base was %x length %x\n",marea->base,marea->length);
191 DPRINT("%s:%d\n",__FILE__,__LINE__);
192 return(0);
193 }
194
195 /*
196 * Call the region specific page fault handler
197 */
198 return(marea->load_page(marea,cr2 - marea->base));
199 }
200
201
202
203 static LPVOID allocate_marea(DWORD dwSize, DWORD flAllocationType,
204 DWORD flProtect, memory_area** list_head,
205 unsigned int last_addr,
206 BOOL (*fn)(memory_area* marea,
207 unsigned int address))
208 /*
209 * FUNCTION:
210 */
211 {
212 memory_area* current=*list_head;
213 memory_area* ndesc=NULL;
214
215 while (current!=NULL)
216 {
217 last_addr = PAGE_ROUND_UP(current->base + current->length);
218 current=current->next;
219 }
220 ndesc = ExAllocatePool(NonPagedPool,sizeof(memory_area));
221 ndesc->access=flProtect;
222 ndesc->state=flAllocationType;
223 ndesc->lock=FALSE;
224 ndesc->base=last_addr+PAGESIZE;
225 ndesc->length=dwSize;
226 ndesc->previous=current;
227 if (current!=NULL)
228 {
229 ndesc->next=current->next;
230 current->next=ndesc;
231 if (current->next!=NULL)
232 {
233 current->next->previous=ndesc;
234 }
235 }
236 else
237 {
238 *list_head=ndesc;
239 ndesc->next=NULL;
240 ndesc->previous=NULL;
241 }
242 ndesc->load_page=fn;
243 DPRINT("VirtualAlloc() returning %x\n",ndesc->base);
244 return((LPVOID)ndesc->base);
245 }
246
247 void commit_region_free(memory_area* marea)
248 /*
249 * FUNCTION: Decommits the region
250 */
251 {
252 int i;
253 for (i=0;i<marea->length;i=i+PAGESIZE)
254 {
255 if (is_page_present(marea->base+marea->length))
256 {
257 free_page(MmGetPhysicalAddress(marea->base+marea->length),1);
258 }
259 }
260 }
261
262 BOOL commit_region_load_page(memory_area* marea, unsigned int address)
263 /*
264 * FUNCTION: Handles a page fault on a committed region of memory
265 * ARGUMENTS:
266 * marea = memory area
267 * address = address of faulting access
268 * RETURNS: TRUE if the page was loaded
269 * FALSE if an exception should be generated
270 */
271 {
272 set_page(marea->base+address,0x7,get_free_page());
273 return(TRUE);
274 }
275
276 asmlinkage LPVOID STDCALL VirtualAlloc(LPVOID lpAddress, DWORD dwSize,
277 DWORD flAllocationType,
278 DWORD flProtect)
279 {
280 /*
281 * FUNCTION: Create a memory area
282 * ARGUMENTS:
283 * lpAddress = the base address of the area or NULL if the system
284 * decides the base
285 * dwSize = the size of the area
286 * flAllocationType = the type of allocation
287 * MEM_COMMIT = accessible
288 * MEM_RESERVE = not accessible but can't be allocated
289 * flProtect = what protection to give the area
290 * RETURNS: The base address of the block
291 */
292 DPRINT("VirtualAlloc() lpAddress %x dwSize %x flAllocationType %x ",
293 lpAddress,dwSize,flAllocationType);
294 DPRINT("flProtect %x\n",flProtect);
295
296 if (lpAddress==NULL)
297 {
298 /*
299 * We decide the address
300 */
301 if (flProtect & PAGE_SYSTEM)
302 {
303 return(allocate_marea(dwSize,flAllocationType,
304 flProtect,&system_memory_area_list_head,
305 KERNEL_BASE,commit_region_load_page));
306 }
307 else
308 {
309 return(allocate_marea(dwSize,flAllocationType,
310 flProtect,&memory_area_list_head,PAGESIZE,
311 commit_region_load_page));
312 }
313 }
314 else
315 {
316 memory_area* marea=NULL;
317 if (lpAddress < ((PVOID)KERNEL_BASE))
318 {
319 marea=find_first_marea(memory_area_list_head,
320 (unsigned int)lpAddress,dwSize);
321 }
322 else
323 {
324 marea=find_first_marea(system_memory_area_list_head,
325 (unsigned int)lpAddress,dwSize);
326 }
327
328 /*
329 * Check someone hasn't already allocated that area
330 */
331 if (marea!=NULL && marea->base <= (unsigned int)lpAddress &&
332 (marea->base + marea->length) > (unsigned int)lpAddress)
333 {
334 SetLastError(ERROR_INVALID_DATA);
335 return(0);
336 }
337
338 /*
339 * Grab the area
340 */
341 }
342 }
343
344 BOOL WINAPI VirtualFreeEx(HANDLE hProcess, LPVOID lpAddress, DWORD dwSize,
345 DWORD dwFreeType)
346 /*
347 * FUNCTION: Releases, decommits or both a region of memory within the
348 * virtual address of a specified process
349 * ARGUMENTS:
350 * hProcess = Process to act on
351 * lpAddress = starting virtual address to free
352 * dwSize = Size in bytes of the memory region to free
353 * dwFreeType = Type of free operation
354 * RETURNS: On success non-zero
355 * On failure zero
356 * NOTE: This tries to optimize for the most common case which is for
357 * regions to be decommitted in large chunks. If a process alternatedly
358 * decommitted every other page in a region this function would be extremely
359 * inefficent.
360 */
361 {
362 memory_area* marea = NULL;
363
364 /*
365 * Check our permissions for hProcess here
366 */
367
368 /*
369 * Get the list of memory areas corresponding to hProcess here
370 */
371
372 /*
373 * Do the business
374 */
375 if (dwFreeType==MEM_RELEASE)
376 {
377 if (dwSize!=0)
378 {
379 SetLastError(ERROR_INVALID_BLOCK_LENGTH);
380 return(0);
381 }
382
383 marea = find_first_marea(memory_area_list_head,(unsigned int)lpAddress,
384 1);
385
386 if (marea==NULL || marea->base!=((int)lpAddress))
387 {
388 SetLastError(ERROR_INVALID_BLOCK);
389 return(0);
390 }
391
392 /*
393 * Ask the memory area to destroy itself
394 */
395 marea->free(marea);
396
397 /*
398 * Change the area attributes
399 */
400 marea->access=PAGE_NOACCESS;
401 marea->state=MEM_FREE;
402 marea->type=MEM_PRIVATE;
403 }
404 else
405 {
406 }
407 }
408
409 BOOL WINAPI VirtualProtectEx(HANDLE hProcess, LPVOID lpAddress,
410 DWORD dwSize, DWORD flNewProtect, PDWORD lpflOldProtect)
411 /*
412 * FUNCTION:
413 */
414 {
415 }
416
417 DWORD WINAPI VirtualQueryEx(HANDLE hProcess, LPCVOID lpAddress,
418 PMEMORY_BASIC_INFORMATION lpBuffer, DWORD dwLength)
419 /*
420 * FUNCTION: Provides information about a range of pages within the virtual
421 * address space of a specified process.
422 * ARGUMENTS:
423 * hProcess = Handle of process
424 * lpAddress = Address of region
425 * lpBuffer = Buffer to store information
426 * dwLength = length of region
427 * RETURNS: The number of bytes transferred into the buffer
428 */
429 {
430 /*
431 * Find the memory area
432 */
433 memory_area* marea = find_first_marea(memory_area_list_head,
434 (unsigned int) lpAddress,
435 dwLength);
436
437 /*
438 * Check it exists
439 */
440 if (marea==NULL || marea->base!=((int)lpAddress))
441 {
442 SetLastError(0);
443 return(0);
444 }
445
446 lpBuffer->BaseAddress = (void *)lpAddress;
447 lpBuffer->AllocationBase = (void *)marea->base;
448 lpBuffer->AllocationProtect = marea->initial_access;
449 lpBuffer->RegionSize = marea->length;
450 lpBuffer->State = marea->state;
451 lpBuffer->Protect = marea->access;
452 lpBuffer->Type = marea->type;
453
454 return(sizeof(MEMORY_BASIC_INFORMATION));
455 }
456
457 BOOLEAN MmIsNonPagedSystemAddressValid(PVOID VirtualAddress)
458 {
459 UNIMPLEMENTED;
460 }
461
462 BOOLEAN MmIsAddressValid(PVOID VirtualAddress)
463 /*
464 * FUNCTION: Checks whether the given address is valid for a read or write
465 * ARGUMENTS:
466 * VirtualAddress = address to check
467 * RETURNS: True if the access would be valid
468 * False if the access would cause a page fault
469 * NOTES: This function checks whether a byte access to the page would
470 * succeed. Is this realistic for RISC processors which don't
471 * allow byte granular access?
472 */
473 {
474 memory_area* marea;
475
476 if (VirtualAddress >= (PVOID)KERNEL_BASE)
477 {
478 marea = find_first_marea(system_memory_area_list_head,
479 (unsigned int)VirtualAddress,1);
480 }
481 else
482 {
483 marea = find_first_marea(memory_area_list_head,
484 (unsigned int)VirtualAddress,1);
485 }
486
487 if (marea==NULL)
488 {
489 return(FALSE);
490 }
491
492 if (marea->access == PAGE_NOACCESS || marea->access & PAGE_GUARD ||
493 marea->state == MEM_FREE || marea->state == MEM_RESERVE)
494 {
495 return(FALSE);
496 }
497
498 return(TRUE);
499 }
500