Commit | Line | Data |
---|---|---|
a99eae54 PM |
1 | /* |
2 | * Alignment access counters and corresponding user-space interfaces. | |
3 | * | |
4 | * Copyright (C) 2009 ST Microelectronics | |
5 | * Copyright (C) 2009 - 2010 Paul Mundt | |
6 | * | |
7 | * This file is subject to the terms and conditions of the GNU General Public | |
8 | * License. See the file "COPYING" in the main directory of this archive | |
9 | * for more details. | |
10 | */ | |
11 | #include <linux/module.h> | |
12 | #include <linux/kernel.h> | |
13 | #include <linux/seq_file.h> | |
14 | #include <linux/proc_fs.h> | |
15 | #include <linux/uaccess.h> | |
16 | #include <asm/alignment.h> | |
94ea5e44 | 17 | #include <asm/processor.h> |
a99eae54 PM |
18 | |
19 | static unsigned long se_user; | |
20 | static unsigned long se_sys; | |
21 | static unsigned long se_half; | |
22 | static unsigned long se_word; | |
23 | static unsigned long se_dword; | |
24 | static unsigned long se_multi; | |
25 | /* bitfield: 1: warn 2: fixup 4: signal -> combinations 2|4 && 1|2|4 are not | |
26 | valid! */ | |
27 | static int se_usermode = UM_WARN | UM_FIXUP; | |
28 | /* 0: no warning 1: print a warning message, disabled by default */ | |
29 | static int se_kernmode_warn; | |
30 | ||
7c1b2c68 PM |
31 | core_param(alignment, se_usermode, int, 0600); |
32 | ||
a99eae54 PM |
33 | void inc_unaligned_byte_access(void) |
34 | { | |
35 | se_half++; | |
36 | } | |
37 | ||
38 | void inc_unaligned_word_access(void) | |
39 | { | |
40 | se_word++; | |
41 | } | |
42 | ||
43 | void inc_unaligned_dword_access(void) | |
44 | { | |
45 | se_dword++; | |
46 | } | |
47 | ||
48 | void inc_unaligned_multi_access(void) | |
49 | { | |
50 | se_multi++; | |
51 | } | |
52 | ||
53 | void inc_unaligned_user_access(void) | |
54 | { | |
55 | se_user++; | |
56 | } | |
57 | ||
58 | void inc_unaligned_kernel_access(void) | |
59 | { | |
60 | se_sys++; | |
61 | } | |
62 | ||
94ea5e44 PM |
63 | /* |
64 | * This defaults to the global policy which can be set from the command | |
65 | * line, while processes can overload their preferences via prctl(). | |
66 | */ | |
a99eae54 PM |
67 | unsigned int unaligned_user_action(void) |
68 | { | |
94ea5e44 PM |
69 | unsigned int action = se_usermode; |
70 | ||
71 | if (current->thread.flags & SH_THREAD_UAC_SIGBUS) { | |
72 | action &= ~UM_FIXUP; | |
73 | action |= UM_SIGNAL; | |
74 | } | |
75 | ||
76 | if (current->thread.flags & SH_THREAD_UAC_NOPRINT) | |
77 | action &= ~UM_WARN; | |
78 | ||
79 | return action; | |
80 | } | |
81 | ||
82 | int get_unalign_ctl(struct task_struct *tsk, unsigned long addr) | |
83 | { | |
84 | return put_user(tsk->thread.flags & SH_THREAD_UAC_MASK, | |
85 | (unsigned int __user *)addr); | |
86 | } | |
87 | ||
88 | int set_unalign_ctl(struct task_struct *tsk, unsigned int val) | |
89 | { | |
90 | tsk->thread.flags = (tsk->thread.flags & ~SH_THREAD_UAC_MASK) | | |
91 | (val & SH_THREAD_UAC_MASK); | |
92 | return 0; | |
a99eae54 PM |
93 | } |
94 | ||
95 | void unaligned_fixups_notify(struct task_struct *tsk, insn_size_t insn, | |
96 | struct pt_regs *regs) | |
97 | { | |
98 | if (user_mode(regs) && (se_usermode & UM_WARN) && printk_ratelimit()) | |
99 | pr_notice("Fixing up unaligned userspace access " | |
100 | "in \"%s\" pid=%d pc=0x%p ins=0x%04hx\n", | |
101 | tsk->comm, task_pid_nr(tsk), | |
88ea1a44 | 102 | (void *)instruction_pointer(regs), insn); |
a99eae54 PM |
103 | else if (se_kernmode_warn && printk_ratelimit()) |
104 | pr_notice("Fixing up unaligned kernel access " | |
105 | "in \"%s\" pid=%d pc=0x%p ins=0x%04hx\n", | |
106 | tsk->comm, task_pid_nr(tsk), | |
88ea1a44 | 107 | (void *)instruction_pointer(regs), insn); |
a99eae54 PM |
108 | } |
109 | ||
110 | static const char *se_usermode_action[] = { | |
111 | "ignored", | |
112 | "warn", | |
113 | "fixup", | |
114 | "fixup+warn", | |
115 | "signal", | |
116 | "signal+warn" | |
117 | }; | |
118 | ||
119 | static int alignment_proc_show(struct seq_file *m, void *v) | |
120 | { | |
121 | seq_printf(m, "User:\t\t%lu\n", se_user); | |
122 | seq_printf(m, "System:\t\t%lu\n", se_sys); | |
123 | seq_printf(m, "Half:\t\t%lu\n", se_half); | |
124 | seq_printf(m, "Word:\t\t%lu\n", se_word); | |
125 | seq_printf(m, "DWord:\t\t%lu\n", se_dword); | |
126 | seq_printf(m, "Multi:\t\t%lu\n", se_multi); | |
127 | seq_printf(m, "User faults:\t%i (%s)\n", se_usermode, | |
128 | se_usermode_action[se_usermode]); | |
129 | seq_printf(m, "Kernel faults:\t%i (fixup%s)\n", se_kernmode_warn, | |
130 | se_kernmode_warn ? "+warn" : ""); | |
131 | return 0; | |
132 | } | |
133 | ||
134 | static int alignment_proc_open(struct inode *inode, struct file *file) | |
135 | { | |
136 | return single_open(file, alignment_proc_show, NULL); | |
137 | } | |
138 | ||
139 | static ssize_t alignment_proc_write(struct file *file, | |
140 | const char __user *buffer, size_t count, loff_t *pos) | |
141 | { | |
142 | int *data = PDE(file->f_path.dentry->d_inode)->data; | |
143 | char mode; | |
144 | ||
145 | if (count > 0) { | |
146 | if (get_user(mode, buffer)) | |
147 | return -EFAULT; | |
148 | if (mode >= '0' && mode <= '5') | |
149 | *data = mode - '0'; | |
150 | } | |
151 | return count; | |
152 | } | |
153 | ||
154 | static const struct file_operations alignment_proc_fops = { | |
155 | .owner = THIS_MODULE, | |
156 | .open = alignment_proc_open, | |
157 | .read = seq_read, | |
158 | .llseek = seq_lseek, | |
159 | .release = single_release, | |
160 | .write = alignment_proc_write, | |
161 | }; | |
162 | ||
163 | /* | |
164 | * This needs to be done after sysctl_init, otherwise sys/ will be | |
165 | * overwritten. Actually, this shouldn't be in sys/ at all since | |
166 | * it isn't a sysctl, and it doesn't contain sysctl information. | |
167 | * We now locate it in /proc/cpu/alignment instead. | |
168 | */ | |
169 | static int __init alignment_init(void) | |
170 | { | |
171 | struct proc_dir_entry *dir, *res; | |
172 | ||
173 | dir = proc_mkdir("cpu", NULL); | |
174 | if (!dir) | |
175 | return -ENOMEM; | |
176 | ||
177 | res = proc_create_data("alignment", S_IWUSR | S_IRUGO, dir, | |
178 | &alignment_proc_fops, &se_usermode); | |
179 | if (!res) | |
180 | return -ENOMEM; | |
181 | ||
182 | res = proc_create_data("kernel_alignment", S_IWUSR | S_IRUGO, dir, | |
183 | &alignment_proc_fops, &se_kernmode_warn); | |
184 | if (!res) | |
185 | return -ENOMEM; | |
186 | ||
187 | return 0; | |
188 | } | |
189 | fs_initcall(alignment_init); |