summaryrefslogtreecommitdiffstats
path: root/drivers/lguest/lguest_user.c
blob: 6ae86f20ce3d6805ee44d7d6721eb8dd33f601a1 (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
/*P:200 This contains all the /dev/lguest code, whereby the userspace launcher
 * controls and communicates with the Guest.  For example, the first write will
 * tell us the memory size, pagetable, entry point and kernel address offset.
 * A read will run the Guest until a signal is pending (-EINTR), or the Guest
 * does a DMA out to the Launcher.  Writes are also used to get a DMA buffer
 * registered by the Guest and to send the Guest an interrupt. :*/
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include "lg.h"

static void setup_regs(struct lguest_regs *regs, unsigned long start)
{
	/* Write out stack in format lguest expects, so we can switch to it. */
	regs->ds = regs->es = regs->ss = __KERNEL_DS|GUEST_PL;
	regs->cs = __KERNEL_CS|GUEST_PL;
	regs->eflags = 0x202; 	/* Interrupts enabled. */
	regs->eip = start;
	/* esi points to our boot information (physical address 0) */
}

/* + addr */
static long user_get_dma(struct lguest *lg, const u32 __user *input)
{
	unsigned long key, udma, irq;

	if (get_user(key, input) != 0)
		return -EFAULT;
	udma = get_dma_buffer(lg, key, &irq);
	if (!udma)
		return -ENOENT;

	/* We put irq number in udma->used_len. */
	lgwrite_u32(lg, udma + offsetof(struct lguest_dma, used_len), irq);
	return udma;
}

/* To force the Guest to stop running and return to the Launcher, the
 * Waker sets writes LHREQ_BREAK and the value "1" to /dev/lguest.  The
 * Launcher then writes LHREQ_BREAK and "0" to release the Waker. */
static int break_guest_out(struct lguest *lg, const u32 __user *input)
{
	unsigned long on;

	/* Fetch whether they're turning break on or off.. */
	if (get_user(on, input) != 0)
		return -EFAULT;

	if (on) {
		lg->break_out = 1;
		/* Pop it out (may be running on different CPU) */
		wake_up_process(lg->tsk);
		/* Wait for them to reset it */
		return wait_event_interruptible(lg->break_wq, !lg->break_out);
	} else {
		lg->break_out = 0;
		wake_up(&lg->break_wq);
		return 0;
	}
}

/* + irq */
static int user_send_irq(struct lguest *lg, const u32 __user *input)
{
	u32 irq;

	if (get_user(irq, input) != 0)
		return -EFAULT;
	if (irq >= LGUEST_IRQS)
		return -EINVAL;
	set_bit(irq, lg->irqs_pending);
	return 0;
}

static ssize_t read(struct file *file, char __user *user, size_t size,loff_t*o)
{
	struct lguest *lg = file->private_data;

	if (!lg)
		return -EINVAL;

	/* If you're not the task which owns the guest, go away. */
	if (current != lg->tsk)
		return -EPERM;

	if (lg->dead) {
		size_t len;

		if (IS_ERR(lg->dead))
			return PTR_ERR(lg->dead);

		len = min(size, strlen(lg->dead)+1);
		if (copy_to_user(user, lg->dead, len) != 0)
			return -EFAULT;
		return len;
	}

	if (lg->dma_is_pending)
		lg->dma_is_pending = 0;

	return run_guest(lg, (unsigned long __user *)user);
}

/* Take: pfnlimit, pgdir, start, pageoffset. */
static int initialize(struct file *file, const u32 __user *input)
{
	struct lguest *lg;
	int err, i;
	u32 args[4];

	/* We grab the Big Lguest lock, which protects the global array
	 * "lguests" and multiple simultaneous initializations. */
	mutex_lock(&lguest_lock);

	if (file->private_data) {
		err = -EBUSY;
		goto unlock;
	}

	if (copy_from_user(args, input, sizeof(args)) != 0) {
		err = -EFAULT;
		goto unlock;
	}

	i = find_free_guest();
	if (i < 0) {
		err = -ENOSPC;
		goto unlock;
	}
	lg = &lguests[i];
	lg->guestid = i;
	lg->pfn_limit = args[0];
	lg->page_offset = args[3];
	lg->regs_page = get_zeroed_page(GFP_KERNEL);
	if (!lg->regs_page) {
		err = -ENOMEM;
		goto release_guest;
	}
	lg->regs = (void *)lg->regs_page + PAGE_SIZE - sizeof(*lg->regs);

	err = init_guest_pagetable(lg, args[1]);
	if (err)
		goto free_regs;

	setup_regs(lg->regs, args[2]);
	setup_guest_gdt(lg);
	init_clockdev(lg);
	lg->tsk = current;
	lg->mm = get_task_mm(lg->tsk);
	init_waitqueue_head(&lg->break_wq);
	lg->last_pages = NULL;
	file->private_data = lg;

	mutex_unlock(&lguest_lock);

	return sizeof(args);

free_regs:
	free_page(lg->regs_page);
release_guest:
	memset(lg, 0, sizeof(*lg));
unlock:
	mutex_unlock(&lguest_lock);
	return err;
}

static ssize_t write(struct file *file, const char __user *input,
		     size_t size, loff_t *off)
{
	struct lguest *lg = file->private_data;
	u32 req;

	if (get_user(req, input) != 0)
		return -EFAULT;
	input += sizeof(req);

	if (req != LHREQ_INITIALIZE && !lg)
		return -EINVAL;
	if (lg && lg->dead)
		return -ENOENT;

	/* If you're not the task which owns the Guest, you can only break */
	if (lg && current != lg->tsk && req != LHREQ_BREAK)
		return -EPERM;

	switch (req) {
	case LHREQ_INITIALIZE:
		return initialize(file, (const u32 __user *)input);
	case LHREQ_GETDMA:
		return user_get_dma(lg, (const u32 __user *)input);
	case LHREQ_IRQ:
		return user_send_irq(lg, (const u32 __user *)input);
	case LHREQ_BREAK:
		return break_guest_out(lg, (const u32 __user *)input);
	default:
		return -EINVAL;
	}
}

static int close(struct inode *inode, struct file *file)
{
	struct lguest *lg = file->private_data;

	if (!lg)
		return 0;

	mutex_lock(&lguest_lock);
	/* Cancels the hrtimer set via LHCALL_SET_CLOCKEVENT. */
	hrtimer_cancel(&lg->hrt);
	release_all_dma(lg);
	free_guest_pagetable(lg);
	mmput(lg->mm);
	if (!IS_ERR(lg->dead))
		kfree(lg->dead);
	free_page(lg->regs_page);
	memset(lg, 0, sizeof(*lg));
	mutex_unlock(&lguest_lock);
	return 0;
}

static struct file_operations lguest_fops = {
	.owner	 = THIS_MODULE,
	.release = close,
	.write	 = write,
	.read	 = read,
};
static struct miscdevice lguest_dev = {
	.minor	= MISC_DYNAMIC_MINOR,
	.name	= "lguest",
	.fops	= &lguest_fops,
};

int __init lguest_device_init(void)
{
	return misc_register(&lguest_dev);
}

void __exit lguest_device_remove(void)
{
	misc_deregister(&lguest_dev);
}
OpenPOWER on IntegriCloud