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 12

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

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

L
Linus Torvalds 已提交
17 18 19 20 21 22 23 24 25
/*
 * 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 已提交
26
		if (desc_empty(&t->tls_array[idx]))
L
Linus Torvalds 已提交
27 28 29 30
			return idx + GDT_ENTRY_TLS_MIN;
	return -ESRCH;
}

31
static void set_tls_desc(struct task_struct *p, int idx,
R
Roland McGrath 已提交
32
			 const struct user_desc *info, int n)
33 34 35 36 37 38 39 40 41 42
{
	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 已提交
43 44 45 46 47 48 49 50
	while (n-- > 0) {
		if (LDT_empty(info))
			desc->a = desc->b = 0;
		else
			fill_ldt(desc, info);
		++info;
		++desc;
	}
51 52 53 54 55 56 57

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

	put_cpu();
}

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

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

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

	/*
	 * index -1 means the kernel should try to find and
	 * allocate an empty descriptor:
	 */
R
Roland McGrath 已提交
77
	if (idx == -1 && can_allocate) {
L
Linus Torvalds 已提交
78 79 80 81 82 83 84 85 86 87
		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 已提交
88
	set_tls_desc(p, idx, &info, 1);
L
Linus Torvalds 已提交
89 90 91 92

	return 0;
}

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


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

105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
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 已提交
123 124 125

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

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

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

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

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

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

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;
}