tls.c 4.8 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/user.h>
R
Roland McGrath 已提交
5
#include <linux/regset.h>
L
Linus Torvalds 已提交
6 7 8 9 10 11

#include <asm/uaccess.h>
#include <asm/desc.h>
#include <asm/ldt.h>
#include <asm/processor.h>
#include <asm/proto.h>
12
#include <asm/syscalls.h>
L
Linus Torvalds 已提交
13

R
Roland McGrath 已提交
14 15
#include "tls.h"

L
Linus Torvalds 已提交
16 17 18 19 20 21 22 23 24
/*
 * sys_alloc_thread_area: get a yet unused TLS descriptor index.
 */
static int get_free_idx(void)
{
	struct thread_struct *t = &current->thread;
	int idx;

	for (idx = 0; idx < GDT_ENTRY_TLS_ENTRIES; idx++)
R
Roland McGrath 已提交
25
		if (desc_empty(&t->tls_array[idx]))
L
Linus Torvalds 已提交
26 27 28 29
			return idx + GDT_ENTRY_TLS_MIN;
	return -ESRCH;
}

30
static void set_tls_desc(struct task_struct *p, int idx,
R
Roland McGrath 已提交
31
			 const struct user_desc *info, int n)
32 33 34 35 36 37 38 39 40 41
{
	struct thread_struct *t = &p->thread;
	struct desc_struct *desc = &t->tls_array[idx - GDT_ENTRY_TLS_MIN];
	int cpu;

	/*
	 * We must not get preempted while modifying the TLS.
	 */
	cpu = get_cpu();

R
Roland McGrath 已提交
42 43 44 45 46 47 48 49
	while (n-- > 0) {
		if (LDT_empty(info))
			desc->a = desc->b = 0;
		else
			fill_ldt(desc, info);
		++info;
		++desc;
	}
50 51 52 53 54 55 56

	if (t == &current->thread)
		load_TLS(t, cpu);

	put_cpu();
}

L
Linus Torvalds 已提交
57 58 59
/*
 * Set a given TLS descriptor:
 */
R
Roland McGrath 已提交
60 61 62
int do_set_thread_area(struct task_struct *p, int idx,
		       struct user_desc __user *u_info,
		       int can_allocate)
L
Linus Torvalds 已提交
63 64 65 66 67 68
{
	struct user_desc info;

	if (copy_from_user(&info, u_info, sizeof(info)))
		return -EFAULT;

R
Roland McGrath 已提交
69 70
	if (idx == -1)
		idx = info.entry_number;
L
Linus Torvalds 已提交
71 72 73 74 75

	/*
	 * index -1 means the kernel should try to find and
	 * allocate an empty descriptor:
	 */
R
Roland McGrath 已提交
76
	if (idx == -1 && can_allocate) {
L
Linus Torvalds 已提交
77 78 79 80 81 82 83 84 85 86
		idx = get_free_idx();
		if (idx < 0)
			return idx;
		if (put_user(idx, &u_info->entry_number))
			return -EFAULT;
	}

	if (idx < GDT_ENTRY_TLS_MIN || idx > GDT_ENTRY_TLS_MAX)
		return -EINVAL;

R
Roland McGrath 已提交
87
	set_tls_desc(p, idx, &info, 1);
L
Linus Torvalds 已提交
88 89 90 91

	return 0;
}

R
Roland McGrath 已提交
92
asmlinkage int sys_set_thread_area(struct user_desc __user *u_info)
R
Roland McGrath 已提交
93
{
R
Roland McGrath 已提交
94
	int ret = do_set_thread_area(current, -1, u_info, 1);
95
	asmlinkage_protect(1, ret, u_info);
R
Roland McGrath 已提交
96
	return ret;
R
Roland McGrath 已提交
97
}
L
Linus Torvalds 已提交
98 99 100 101 102 103


/*
 * Get the current Thread-Local Storage area:
 */

104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
static void fill_user_desc(struct user_desc *info, int idx,
			   const struct desc_struct *desc)

{
	memset(info, 0, sizeof(*info));
	info->entry_number = idx;
	info->base_addr = get_desc_base(desc);
	info->limit = get_desc_limit(desc);
	info->seg_32bit = desc->d;
	info->contents = desc->type >> 2;
	info->read_exec_only = !(desc->type & 2);
	info->limit_in_pages = desc->g;
	info->seg_not_present = !desc->p;
	info->useable = desc->avl;
#ifdef CONFIG_X86_64
	info->lm = desc->l;
#endif
}
R
Roland McGrath 已提交
122 123 124

int do_get_thread_area(struct task_struct *p, int idx,
		       struct user_desc __user *u_info)
L
Linus Torvalds 已提交
125 126 127
{
	struct user_desc info;

R
Roland McGrath 已提交
128
	if (idx == -1 && get_user(idx, &u_info->entry_number))
L
Linus Torvalds 已提交
129
		return -EFAULT;
130

L
Linus Torvalds 已提交
131 132 133
	if (idx < GDT_ENTRY_TLS_MIN || idx > GDT_ENTRY_TLS_MAX)
		return -EINVAL;

134 135
	fill_user_desc(&info, idx,
		       &p->thread.tls_array[idx - GDT_ENTRY_TLS_MIN]);
L
Linus Torvalds 已提交
136 137 138 139 140 141

	if (copy_to_user(u_info, &info, sizeof(info)))
		return -EFAULT;
	return 0;
}

R
Roland McGrath 已提交
142
asmlinkage int sys_get_thread_area(struct user_desc __user *u_info)
L
Linus Torvalds 已提交
143
{
R
Roland McGrath 已提交
144
	int ret = do_get_thread_area(current, -1, u_info);
145
	asmlinkage_protect(1, ret, u_info);
R
Roland McGrath 已提交
146
	return ret;
L
Linus Torvalds 已提交
147
}
R
Roland McGrath 已提交
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

int regset_tls_active(struct task_struct *target,
		      const struct user_regset *regset)
{
	struct thread_struct *t = &target->thread;
	int n = GDT_ENTRY_TLS_ENTRIES;
	while (n > 0 && desc_empty(&t->tls_array[n - 1]))
		--n;
	return n;
}

int regset_tls_get(struct task_struct *target, const struct user_regset *regset,
		   unsigned int pos, unsigned int count,
		   void *kbuf, void __user *ubuf)
{
	const struct desc_struct *tls;

	if (pos > GDT_ENTRY_TLS_ENTRIES * sizeof(struct user_desc) ||
	    (pos % sizeof(struct user_desc)) != 0 ||
	    (count % sizeof(struct user_desc)) != 0)
		return -EINVAL;

	pos /= sizeof(struct user_desc);
	count /= sizeof(struct user_desc);

	tls = &target->thread.tls_array[pos];

	if (kbuf) {
		struct user_desc *info = kbuf;
		while (count-- > 0)
			fill_user_desc(info++, GDT_ENTRY_TLS_MIN + pos++,
				       tls++);
	} else {
		struct user_desc __user *u_info = ubuf;
		while (count-- > 0) {
			struct user_desc info;
			fill_user_desc(&info, GDT_ENTRY_TLS_MIN + pos++, tls++);
			if (__copy_to_user(u_info++, &info, sizeof(info)))
				return -EFAULT;
		}
	}

	return 0;
}

int regset_tls_set(struct task_struct *target, const struct user_regset *regset,
		   unsigned int pos, unsigned int count,
		   const void *kbuf, const void __user *ubuf)
{
	struct user_desc infobuf[GDT_ENTRY_TLS_ENTRIES];
	const struct user_desc *info;

	if (pos > GDT_ENTRY_TLS_ENTRIES * sizeof(struct user_desc) ||
	    (pos % sizeof(struct user_desc)) != 0 ||
	    (count % sizeof(struct user_desc)) != 0)
		return -EINVAL;

	if (kbuf)
		info = kbuf;
	else if (__copy_from_user(infobuf, ubuf, count))
		return -EFAULT;
	else
		info = infobuf;

	set_tls_desc(target,
		     GDT_ENTRY_TLS_MIN + (pos / sizeof(struct user_desc)),
		     info, count / sizeof(struct user_desc));

	return 0;
}