aboutsummaryrefslogtreecommitdiffstats
path: root/arch/sh/mm/alignment.c
blob: 2da808802a4c6e3c24bea5bd1ca405deff883b53 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
 * Alignment access counters and corresponding user-space interfaces.
 *
 * Copyright (C) 2009 ST Microelectronics
 * Copyright (C) 2009 - 2010 Paul Mundt
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <asm/alignment.h>

static unsigned long se_user;
static unsigned long se_sys;
static unsigned long se_half;
static unsigned long se_word;
static unsigned long se_dword;
static unsigned long se_multi;
/* bitfield: 1: warn 2: fixup 4: signal -> combinations 2|4 && 1|2|4 are not
   valid! */
static int se_usermode = UM_WARN | UM_FIXUP;
/* 0: no warning 1: print a warning message, disabled by default */
static int se_kernmode_warn;

void inc_unaligned_byte_access(void)
{
	se_half++;
}

void inc_unaligned_word_access(void)
{
	se_word++;
}

void inc_unaligned_dword_access(void)
{
	se_dword++;
}

void inc_unaligned_multi_access(void)
{
	se_multi++;
}

void inc_unaligned_user_access(void)
{
	se_user++;
}

void inc_unaligned_kernel_access(void)
{
	se_sys++;
}

unsigned int unaligned_user_action(void)
{
	return se_usermode;
}

void unaligned_fixups_notify(struct task_struct *tsk, insn_size_t insn,
			     struct pt_regs *regs)
{
	if (user_mode(regs) && (se_usermode & UM_WARN) && printk_ratelimit())
		pr_notice("Fixing up unaligned userspace access "
			  "in \"%s\" pid=%d pc=0x%p ins=0x%04hx\n",
			  tsk->comm, task_pid_nr(tsk),
			  (void *)instruction_pointer(regs), insn);
	else if (se_kernmode_warn && printk_ratelimit())
		pr_notice("Fixing up unaligned kernel access "
			  "in \"%s\" pid=%d pc=0x%p ins=0x%04hx\n",
			  tsk->comm, task_pid_nr(tsk),
			  (void *)instruction_pointer(regs), insn);
}

static const char *se_usermode_action[] = {
	"ignored",
	"warn",
	"fixup",
	"fixup+warn",
	"signal",
	"signal+warn"
};

static int alignment_proc_show(struct seq_file *m, void *v)
{
	seq_printf(m, "User:\t\t%lu\n", se_user);
	seq_printf(m, "System:\t\t%lu\n", se_sys);
	seq_printf(m, "Half:\t\t%lu\n", se_half);
	seq_printf(m, "Word:\t\t%lu\n", se_word);
	seq_printf(m, "DWord:\t\t%lu\n", se_dword);
	seq_printf(m, "Multi:\t\t%lu\n", se_multi);
	seq_printf(m, "User faults:\t%i (%s)\n", se_usermode,
			se_usermode_action[se_usermode]);
	seq_printf(m, "Kernel faults:\t%i (fixup%s)\n", se_kernmode_warn,
			se_kernmode_warn ? "+warn" : "");
	return 0;
}

static int alignment_proc_open(struct inode *inode, struct file *file)
{
	return single_open(file, alignment_proc_show, NULL);
}

static ssize_t alignment_proc_write(struct file *file,
		const char __user *buffer, size_t count, loff_t *pos)
{
	int *data = PDE(file->f_path.dentry->d_inode)->data;
	char mode;

	if (count > 0) {
		if (get_user(mode, buffer))
			return -EFAULT;
		if (mode >= '0' && mode <= '5')
			*data = mode - '0';
	}
	return count;
}

static const struct file_operations alignment_proc_fops = {
	.owner		= THIS_MODULE,
	.open		= alignment_proc_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= single_release,
	.write		= alignment_proc_write,
};

/*
 * This needs to be done after sysctl_init, otherwise sys/ will be
 * overwritten.  Actually, this shouldn't be in sys/ at all since
 * it isn't a sysctl, and it doesn't contain sysctl information.
 * We now locate it in /proc/cpu/alignment instead.
 */
static int __init alignment_init(void)
{
	struct proc_dir_entry *dir, *res;

	dir = proc_mkdir("cpu", NULL);
	if (!dir)
		return -ENOMEM;

	res = proc_create_data("alignment", S_IWUSR | S_IRUGO, dir,
			       &alignment_proc_fops, &se_usermode);
	if (!res)
		return -ENOMEM;

        res = proc_create_data("kernel_alignment", S_IWUSR | S_IRUGO, dir,
			       &alignment_proc_fops, &se_kernmode_warn);
        if (!res)
                return -ENOMEM;

	return 0;
}
fs_initcall(alignment_init);