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
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
219
220
221
222
223
224
225
226
227
228
229
|
/* Copyright (c) 2014, Motorola Mobility LLC. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#define pr_fmt(fmt) "mot_patch: " fmt
#include <linux/kernel.h>
#include <linux/of_fdt.h>
#include <linux/printk.h>
#include <linux/string.h>
#include <asm/cacheflush.h>
#include <asm/page.h>
#include <asm/sections.h>
#define MOT_PATCH_BUFFER_CANARY 0xABAD1DEA
#define MOT_PATCH_MEMORY_TYPE 0x80000001
#define MOT_PATCH_CODE_TYPE 0x80000002
#define MAX_SUPPORTED_BUFFER 1024
#define MOT_PATCH_ALIGN_SIZE 2048
#define MOT_PATCH_MAX_SIZE 0x4000 /* 16kb */
struct mp_header {
unsigned int id;
unsigned int size;
unsigned int version;
unsigned int banner_size;
};
struct mp_footer {
unsigned int id;
};
struct mp_patch {
unsigned int type;
unsigned long address;
unsigned int data_length;
};
/* Dummy reserved executable region to be used as scratch space to
* hold code fixups needed that cannot be patched within the function
*/
__aligned(16)
void mot_patch_table(void)
{
/* Just reserve with NOPs */
asm(
".rept 1023\n\t"
"mov r0, r0\n\t"
".endr\n\t"
);
}
static inline unsigned char *get_data_start(unsigned char *base)
{
struct mp_header *hdr = (struct mp_header *)base;
unsigned int size = le32_to_cpu(hdr->banner_size);
return base + sizeof(struct mp_header) + ALIGN(size, 4);
}
static inline unsigned char *get_data_end(unsigned char *base)
{
struct mp_header *hdr = (struct mp_header *)base;
unsigned int data_size;
data_size = le32_to_cpu(hdr->size) - sizeof(struct mp_footer);
return base + data_size;
}
static inline struct mp_footer *get_footer(unsigned char *base)
{
return (struct mp_footer *)get_data_end(base);
}
static inline bool valid_canary(unsigned int id)
{
return le32_to_cpu(id) == MOT_PATCH_BUFFER_CANARY;
}
static inline bool valid_banner(struct mp_header *hdr)
{
int max_size = strlen(linux_banner);
char *ptr = (char *)hdr + sizeof(struct mp_header);
if (le32_to_cpu(hdr->banner_size) > max_size) {
pr_err("banner size too large\n");
return false;
}
if (strncmp(ptr, linux_banner, max_size)) {
pr_err("banner mismatch\n");
return false;
}
return true;
}
static int validate_buffer(unsigned char *base)
{
struct mp_header *hdr = (struct mp_header *)base;
unsigned char *ptr = base;
struct mp_footer *ftr;
int count = 0;
if (!valid_canary(hdr->id)) {
pr_err("Invalid starting marker, skipping\n");
return 0;
}
if (le32_to_cpu(hdr->size) > MOT_PATCH_MAX_SIZE) {
pr_err("Invalid size argument: 0x%X\n",
le32_to_cpu(hdr->size));
return 0;
}
if (!valid_banner(hdr))
return 0;
ptr = get_data_start(base);
/* While there is still expected data */
while (ptr < get_data_end(base)) {
struct mp_patch *patch = (struct mp_patch *)ptr;
unsigned int size = le32_to_cpu(patch->data_length);
/* Validate the type */
switch (le32_to_cpu(patch->type)) {
case MOT_PATCH_MEMORY_TYPE:
case MOT_PATCH_CODE_TYPE:
break;
default:
pr_err("Invalid patch type: %X\n",
le32_to_cpu(patch->type));
return 0;
}
if (size > MAX_SUPPORTED_BUFFER) {
pr_err("data_length exceeds support\n");
return 0;
}
count++;
/* Increment the pointer a patch structure and data size */
ptr += size + sizeof(struct mp_patch);
}
ftr = get_footer(base);
if (!valid_canary(ftr->id)) {
pr_err("Missing end marker: %X\n", le32_to_cpu(ftr->id));
return 0;
}
return count;
}
static unsigned char *locate_patch_buffer(unsigned int atags)
{
struct boot_param_header *dt;
unsigned int size;
dt = phys_to_virt(atags);
/* Check for a devtree */
if (be32_to_cpu(dt->magic) != OF_DT_HEADER)
return NULL;
size = be32_to_cpu(dt->totalsize);
if (!size)
return NULL;
size = ALIGN(size, MOT_PATCH_ALIGN_SIZE);
return (unsigned char *)dt + size;
}
void mot_patch_kernel(unsigned int atags)
{
unsigned char *ptr, *base;
int count;
/* Try to find the patch buffer attached to end of devtree */
base = locate_patch_buffer(atags);
if (!base)
return;
/* Do a sanity of all the contents before applying a single
* patch so we are not partially patching from the buffer
*/
count = validate_buffer(base);
if (!count)
return;
pr_info("Applying %d patches\n", count);
ptr = get_data_start(base);
/* While there is still expected data */
while (ptr < get_data_end(base)) {
struct mp_patch *patch = (struct mp_patch *)ptr;
unsigned int psize;
psize = sizeof(*patch) + le32_to_cpu(patch->data_length);
memcpy((void *)le32_to_cpu(patch->address),
(void *)patch + sizeof(*patch),
le32_to_cpu(patch->data_length));
ptr += psize;
}
flush_cache_all();
return;
}
|