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 <linux/ratelimit.h> |
17 | #include <asm/alignment.h> |
18 | #include <asm/processor.h> |
19 | |
20 | static unsigned long se_user; |
21 | static unsigned long se_sys; |
22 | static unsigned long se_half; |
23 | static unsigned long se_word; |
24 | static unsigned long se_dword; |
25 | static unsigned long se_multi; |
26 | /* bitfield: 1: warn 2: fixup 4: signal -> combinations 2|4 && 1|2|4 are not |
27 | valid! */ |
28 | static int se_usermode = UM_WARN | UM_FIXUP; |
29 | /* 0: no warning 1: print a warning message, disabled by default */ |
30 | static int se_kernmode_warn; |
31 | |
32 | core_param(alignment, se_usermode, int, 0600); |
33 | |
34 | void inc_unaligned_byte_access(void) |
35 | { |
36 | se_half++; |
37 | } |
38 | |
39 | void inc_unaligned_word_access(void) |
40 | { |
41 | se_word++; |
42 | } |
43 | |
44 | void inc_unaligned_dword_access(void) |
45 | { |
46 | se_dword++; |
47 | } |
48 | |
49 | void inc_unaligned_multi_access(void) |
50 | { |
51 | se_multi++; |
52 | } |
53 | |
54 | void inc_unaligned_user_access(void) |
55 | { |
56 | se_user++; |
57 | } |
58 | |
59 | void inc_unaligned_kernel_access(void) |
60 | { |
61 | se_sys++; |
62 | } |
63 | |
64 | /* |
65 | * This defaults to the global policy which can be set from the command |
66 | * line, while processes can overload their preferences via prctl(). |
67 | */ |
68 | unsigned int unaligned_user_action(void) |
69 | { |
70 | unsigned int action = se_usermode; |
71 | |
72 | if (current->thread.flags & SH_THREAD_UAC_SIGBUS) { |
73 | action &= ~UM_FIXUP; |
74 | action |= UM_SIGNAL; |
75 | } |
76 | |
77 | if (current->thread.flags & SH_THREAD_UAC_NOPRINT) |
78 | action &= ~UM_WARN; |
79 | |
80 | return action; |
81 | } |
82 | |
83 | int get_unalign_ctl(struct task_struct *tsk, unsigned long addr) |
84 | { |
85 | return put_user(tsk->thread.flags & SH_THREAD_UAC_MASK, |
86 | (unsigned int __user *)addr); |
87 | } |
88 | |
89 | int set_unalign_ctl(struct task_struct *tsk, unsigned int val) |
90 | { |
91 | tsk->thread.flags = (tsk->thread.flags & ~SH_THREAD_UAC_MASK) | |
92 | (val & SH_THREAD_UAC_MASK); |
93 | return 0; |
94 | } |
95 | |
96 | void unaligned_fixups_notify(struct task_struct *tsk, insn_size_t insn, |
97 | struct pt_regs *regs) |
98 | { |
99 | if (user_mode(regs) && (se_usermode & UM_WARN)) |
100 | pr_notice_ratelimited("Fixing up unaligned userspace access " |
101 | "in \"%s\" pid=%d pc=0x%p ins=0x%04hx\n" , |
102 | tsk->comm, task_pid_nr(tsk), |
103 | (void *)instruction_pointer(regs), insn); |
104 | else if (se_kernmode_warn) |
105 | pr_notice_ratelimited("Fixing up unaligned kernel access " |
106 | "in \"%s\" pid=%d pc=0x%p ins=0x%04hx\n" , |
107 | tsk->comm, task_pid_nr(tsk), |
108 | (void *)instruction_pointer(regs), insn); |
109 | } |
110 | |
111 | static const char *se_usermode_action[] = { |
112 | "ignored" , |
113 | "warn" , |
114 | "fixup" , |
115 | "fixup+warn" , |
116 | "signal" , |
117 | "signal+warn" |
118 | }; |
119 | |
120 | static int alignment_proc_show(struct seq_file *m, void *v) |
121 | { |
122 | seq_printf(m, fmt: "User:\t\t%lu\n" , se_user); |
123 | seq_printf(m, fmt: "System:\t\t%lu\n" , se_sys); |
124 | seq_printf(m, fmt: "Half:\t\t%lu\n" , se_half); |
125 | seq_printf(m, fmt: "Word:\t\t%lu\n" , se_word); |
126 | seq_printf(m, fmt: "DWord:\t\t%lu\n" , se_dword); |
127 | seq_printf(m, fmt: "Multi:\t\t%lu\n" , se_multi); |
128 | seq_printf(m, fmt: "User faults:\t%i (%s)\n" , se_usermode, |
129 | se_usermode_action[se_usermode]); |
130 | seq_printf(m, fmt: "Kernel faults:\t%i (fixup%s)\n" , se_kernmode_warn, |
131 | se_kernmode_warn ? "+warn" : "" ); |
132 | return 0; |
133 | } |
134 | |
135 | static int alignment_proc_open(struct inode *inode, struct file *file) |
136 | { |
137 | return single_open(file, alignment_proc_show, NULL); |
138 | } |
139 | |
140 | static ssize_t alignment_proc_write(struct file *file, |
141 | const char __user *buffer, size_t count, loff_t *pos) |
142 | { |
143 | int *data = pde_data(inode: file_inode(f: file)); |
144 | char mode; |
145 | |
146 | if (count > 0) { |
147 | if (get_user(mode, buffer)) |
148 | return -EFAULT; |
149 | if (mode >= '0' && mode <= '5') |
150 | *data = mode - '0'; |
151 | } |
152 | return count; |
153 | } |
154 | |
155 | static const struct proc_ops alignment_proc_ops = { |
156 | .proc_open = alignment_proc_open, |
157 | .proc_read = seq_read, |
158 | .proc_lseek = seq_lseek, |
159 | .proc_release = single_release, |
160 | .proc_write = alignment_proc_write, |
161 | }; |
162 | |
163 | /* |
164 | * This needs to be done after sysctl_init_bases(), 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_ops, &se_usermode); |
179 | if (!res) |
180 | return -ENOMEM; |
181 | |
182 | res = proc_create_data("kernel_alignment" , S_IWUSR | S_IRUGO, dir, |
183 | &alignment_proc_ops, &se_kernmode_warn); |
184 | if (!res) |
185 | return -ENOMEM; |
186 | |
187 | return 0; |
188 | } |
189 | fs_initcall(alignment_init); |
190 | |