summaryrefslogtreecommitdiff
path: root/src/kernel/mem/virt.c
blob: a9450c7fc0fc831cc5cd30facc80ba0c348b31b4 (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
#include <kernel/arch/generic.h>
#include <kernel/mem/virt.h>
#include <kernel/util.h>

void virt_iter_new(
		struct virt_iter *iter, void __user *virt, size_t length,
		struct pagedir *pages, bool user, bool writeable)
{
	iter->frag       = 0;
	iter->frag_len   = 0;
	iter->prior      = 0;
	iter->error      = false;
	iter->_virt      = virt;
	iter->_remaining = length;
	iter->_pages     = pages;
	iter->_user      = user;
	iter->_writeable = writeable;
}

bool virt_iter_next(struct virt_iter *iter) {
	/* note: While i'm pretty sure that this should always work, this
	 * was only tested in cases where the pages were consecutive both in
	 * virtual and physical memory, which might not always be the case.
	 * TODO test this */

	size_t partial = iter->_remaining;
	iter->prior   += iter->frag_len;
	if (partial <= 0) return false;

	if (iter->_pages) { // if iterating over virtual memory
		// don't read past the page
		if (((uintptr_t)iter->_virt & PAGE_MASK) + partial > PAGE_SIZE)
			partial = PAGE_SIZE - ((uintptr_t)iter->_virt & PAGE_MASK);

		iter->frag = pagedir_virt2phys(iter->_pages,
				iter->_virt, iter->_user, iter->_writeable);

		if (iter->frag == 0) {
			iter->error = true;
			return false;
		}
	} else {
		// "iterate" over physical memory
		// the double cast supresses the warning about changing address spaces
		iter->frag = (void* __force)iter->_virt;
	}

	iter->frag_len    = partial;
	iter->_remaining -= partial;
	iter->_virt      += partial;
	return true;
}

bool virt_cpy(
		struct pagedir *dest_pages,       void __user *dest,
		struct pagedir  *src_pages, const void __user *src, size_t length)
{
	struct virt_iter dest_iter, src_iter;
	size_t min;

	virt_iter_new(&dest_iter,           dest, length, dest_pages, true, true);
	virt_iter_new( &src_iter, (userptr_t)src, length,  src_pages, true, false);
	dest_iter.frag_len = 0;
	src_iter.frag_len  = 0;

	for (;;) {
		if (dest_iter.frag_len <= 0)
			if (!virt_iter_next(&dest_iter)) break;
		if ( src_iter.frag_len <= 0)
			if (!virt_iter_next( &src_iter)) break;

		min = src_iter.frag_len < dest_iter.frag_len
		    ? src_iter.frag_len : dest_iter.frag_len;
		memcpy(dest_iter.frag, src_iter.frag, min);

		dest_iter.frag_len -= min;
		dest_iter.frag     += min;
		src_iter.frag_len  -= min;
		src_iter.frag      += min;
	}

	return !(dest_iter.error || src_iter.error);
}