diff options
Diffstat (limited to 'arch/x86/kernel/tls.c')
-rw-r--r-- | arch/x86/kernel/tls.c | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/arch/x86/kernel/tls.c b/arch/x86/kernel/tls.c new file mode 100644 index 000000000000..5291596f19b0 --- /dev/null +++ b/arch/x86/kernel/tls.c | |||
@@ -0,0 +1,158 @@ | |||
1 | #include <linux/kernel.h> | ||
2 | #include <linux/errno.h> | ||
3 | #include <linux/sched.h> | ||
4 | #include <linux/user.h> | ||
5 | |||
6 | #include <asm/uaccess.h> | ||
7 | #include <asm/desc.h> | ||
8 | #include <asm/system.h> | ||
9 | #include <asm/ldt.h> | ||
10 | #include <asm/processor.h> | ||
11 | #include <asm/proto.h> | ||
12 | |||
13 | /* | ||
14 | * sys_alloc_thread_area: get a yet unused TLS descriptor index. | ||
15 | */ | ||
16 | static int get_free_idx(void) | ||
17 | { | ||
18 | struct thread_struct *t = ¤t->thread; | ||
19 | int idx; | ||
20 | |||
21 | for (idx = 0; idx < GDT_ENTRY_TLS_ENTRIES; idx++) | ||
22 | if (desc_empty((struct n_desc_struct *)(t->tls_array) + idx)) | ||
23 | return idx + GDT_ENTRY_TLS_MIN; | ||
24 | return -ESRCH; | ||
25 | } | ||
26 | |||
27 | /* | ||
28 | * Set a given TLS descriptor: | ||
29 | * When you want addresses > 32bit use arch_prctl() | ||
30 | */ | ||
31 | int do_set_thread_area(struct thread_struct *t, struct user_desc __user *u_info) | ||
32 | { | ||
33 | struct user_desc info; | ||
34 | struct n_desc_struct *desc; | ||
35 | int cpu, idx; | ||
36 | |||
37 | if (copy_from_user(&info, u_info, sizeof(info))) | ||
38 | return -EFAULT; | ||
39 | |||
40 | idx = info.entry_number; | ||
41 | |||
42 | /* | ||
43 | * index -1 means the kernel should try to find and | ||
44 | * allocate an empty descriptor: | ||
45 | */ | ||
46 | if (idx == -1) { | ||
47 | idx = get_free_idx(); | ||
48 | if (idx < 0) | ||
49 | return idx; | ||
50 | if (put_user(idx, &u_info->entry_number)) | ||
51 | return -EFAULT; | ||
52 | } | ||
53 | |||
54 | if (idx < GDT_ENTRY_TLS_MIN || idx > GDT_ENTRY_TLS_MAX) | ||
55 | return -EINVAL; | ||
56 | |||
57 | desc = ((struct n_desc_struct *)t->tls_array) + idx - GDT_ENTRY_TLS_MIN; | ||
58 | |||
59 | /* | ||
60 | * We must not get preempted while modifying the TLS. | ||
61 | */ | ||
62 | cpu = get_cpu(); | ||
63 | |||
64 | if (LDT_empty(&info)) { | ||
65 | desc->a = 0; | ||
66 | desc->b = 0; | ||
67 | } else { | ||
68 | desc->a = LDT_entry_a(&info); | ||
69 | desc->b = LDT_entry_b(&info); | ||
70 | } | ||
71 | if (t == ¤t->thread) | ||
72 | load_TLS(t, cpu); | ||
73 | |||
74 | put_cpu(); | ||
75 | return 0; | ||
76 | } | ||
77 | |||
78 | asmlinkage long sys32_set_thread_area(struct user_desc __user *u_info) | ||
79 | { | ||
80 | return do_set_thread_area(¤t->thread, u_info); | ||
81 | } | ||
82 | |||
83 | |||
84 | /* | ||
85 | * Get the current Thread-Local Storage area: | ||
86 | */ | ||
87 | |||
88 | #define GET_LIMIT(desc) ( \ | ||
89 | ((desc)->a & 0x0ffff) | \ | ||
90 | ((desc)->b & 0xf0000) ) | ||
91 | |||
92 | #define GET_32BIT(desc) (((desc)->b >> 22) & 1) | ||
93 | #define GET_CONTENTS(desc) (((desc)->b >> 10) & 3) | ||
94 | #define GET_WRITABLE(desc) (((desc)->b >> 9) & 1) | ||
95 | #define GET_LIMIT_PAGES(desc) (((desc)->b >> 23) & 1) | ||
96 | #define GET_PRESENT(desc) (((desc)->b >> 15) & 1) | ||
97 | #define GET_USEABLE(desc) (((desc)->b >> 20) & 1) | ||
98 | #define GET_LONGMODE(desc) (((desc)->b >> 21) & 1) | ||
99 | |||
100 | int do_get_thread_area(struct thread_struct *t, struct user_desc __user *u_info) | ||
101 | { | ||
102 | struct user_desc info; | ||
103 | struct n_desc_struct *desc; | ||
104 | int idx; | ||
105 | |||
106 | if (get_user(idx, &u_info->entry_number)) | ||
107 | return -EFAULT; | ||
108 | if (idx < GDT_ENTRY_TLS_MIN || idx > GDT_ENTRY_TLS_MAX) | ||
109 | return -EINVAL; | ||
110 | |||
111 | desc = ((struct n_desc_struct *)t->tls_array) + idx - GDT_ENTRY_TLS_MIN; | ||
112 | |||
113 | memset(&info, 0, sizeof(struct user_desc)); | ||
114 | info.entry_number = idx; | ||
115 | info.base_addr = get_desc_base(desc); | ||
116 | info.limit = GET_LIMIT(desc); | ||
117 | info.seg_32bit = GET_32BIT(desc); | ||
118 | info.contents = GET_CONTENTS(desc); | ||
119 | info.read_exec_only = !GET_WRITABLE(desc); | ||
120 | info.limit_in_pages = GET_LIMIT_PAGES(desc); | ||
121 | info.seg_not_present = !GET_PRESENT(desc); | ||
122 | info.useable = GET_USEABLE(desc); | ||
123 | info.lm = GET_LONGMODE(desc); | ||
124 | |||
125 | if (copy_to_user(u_info, &info, sizeof(info))) | ||
126 | return -EFAULT; | ||
127 | return 0; | ||
128 | } | ||
129 | |||
130 | asmlinkage long sys32_get_thread_area(struct user_desc __user *u_info) | ||
131 | { | ||
132 | return do_get_thread_area(¤t->thread, u_info); | ||
133 | } | ||
134 | |||
135 | |||
136 | int ia32_child_tls(struct task_struct *p, struct pt_regs *childregs) | ||
137 | { | ||
138 | struct n_desc_struct *desc; | ||
139 | struct user_desc info; | ||
140 | struct user_desc __user *cp; | ||
141 | int idx; | ||
142 | |||
143 | cp = (void __user *)childregs->rsi; | ||
144 | if (copy_from_user(&info, cp, sizeof(info))) | ||
145 | return -EFAULT; | ||
146 | if (LDT_empty(&info)) | ||
147 | return -EINVAL; | ||
148 | |||
149 | idx = info.entry_number; | ||
150 | if (idx < GDT_ENTRY_TLS_MIN || idx > GDT_ENTRY_TLS_MAX) | ||
151 | return -EINVAL; | ||
152 | |||
153 | desc = (struct n_desc_struct *)(p->thread.tls_array) + idx - GDT_ENTRY_TLS_MIN; | ||
154 | desc->a = LDT_entry_a(&info); | ||
155 | desc->b = LDT_entry_b(&info); | ||
156 | |||
157 | return 0; | ||
158 | } | ||