]>
Commit | Line | Data |
---|---|---|
00a3e2e9 | 1 | /* |
ca94297f | 2 | * PS3 flash memory os area. |
00a3e2e9 GL |
3 | * |
4 | * Copyright (C) 2006 Sony Computer Entertainment Inc. | |
5 | * Copyright 2006 Sony Corp. | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; version 2 of the License. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program; if not, write to the Free Software | |
18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
19 | */ | |
20 | ||
21 | #include <linux/kernel.h> | |
22 | #include <linux/io.h> | |
418ef209 | 23 | #include <linux/workqueue.h> |
ef2ac63a GL |
24 | #include <linux/fs.h> |
25 | #include <linux/syscalls.h> | |
66b15db6 | 26 | #include <linux/export.h> |
ec5d2dfe | 27 | #include <linux/ctype.h> |
95f72d1e | 28 | #include <linux/memblock.h> |
e988a139 | 29 | #include <linux/of.h> |
5a0e3ad6 | 30 | #include <linux/slab.h> |
00a3e2e9 | 31 | |
d9b2b2a2 | 32 | #include <asm/prom.h> |
00a3e2e9 GL |
33 | |
34 | #include "platform.h" | |
35 | ||
36 | enum { | |
37 | OS_AREA_SEGMENT_SIZE = 0X200, | |
38 | }; | |
39 | ||
ca94297f | 40 | enum os_area_ldr_format { |
00a3e2e9 GL |
41 | HEADER_LDR_FORMAT_RAW = 0, |
42 | HEADER_LDR_FORMAT_GZIP = 1, | |
43 | }; | |
44 | ||
ec5d2dfe GL |
45 | #define OS_AREA_HEADER_MAGIC_NUM "cell_ext_os_area" |
46 | ||
00a3e2e9 GL |
47 | /** |
48 | * struct os_area_header - os area header segment. | |
49 | * @magic_num: Always 'cell_ext_os_area'. | |
50 | * @hdr_version: Header format version number. | |
ef2ac63a | 51 | * @db_area_offset: Starting segment number of other os database area. |
00a3e2e9 GL |
52 | * @ldr_area_offset: Starting segment number of bootloader image area. |
53 | * @ldr_format: HEADER_LDR_FORMAT flag. | |
54 | * @ldr_size: Size of bootloader image in bytes. | |
55 | * | |
56 | * Note that the docs refer to area offsets. These are offsets in units of | |
57 | * segments from the start of the os area (top of the header). These are | |
58 | * better thought of as segment numbers. The os area of the os area is | |
59 | * reserved for the os image. | |
60 | */ | |
61 | ||
62 | struct os_area_header { | |
ca94297f | 63 | u8 magic_num[16]; |
00a3e2e9 | 64 | u32 hdr_version; |
ef2ac63a | 65 | u32 db_area_offset; |
00a3e2e9 GL |
66 | u32 ldr_area_offset; |
67 | u32 _reserved_1; | |
68 | u32 ldr_format; | |
69 | u32 ldr_size; | |
70 | u32 _reserved_2[6]; | |
a8229a9e | 71 | }; |
00a3e2e9 | 72 | |
ca94297f | 73 | enum os_area_boot_flag { |
00a3e2e9 GL |
74 | PARAM_BOOT_FLAG_GAME_OS = 0, |
75 | PARAM_BOOT_FLAG_OTHER_OS = 1, | |
76 | }; | |
77 | ||
ca94297f | 78 | enum os_area_ctrl_button { |
00a3e2e9 GL |
79 | PARAM_CTRL_BUTTON_O_IS_YES = 0, |
80 | PARAM_CTRL_BUTTON_X_IS_YES = 1, | |
81 | }; | |
82 | ||
83 | /** | |
84 | * struct os_area_params - os area params segment. | |
85 | * @boot_flag: User preference of operating system, PARAM_BOOT_FLAG flag. | |
86 | * @num_params: Number of params in this (params) segment. | |
87 | * @rtc_diff: Difference in seconds between 1970 and the ps3 rtc value. | |
88 | * @av_multi_out: User preference of AV output, PARAM_AV_MULTI_OUT flag. | |
89 | * @ctrl_button: User preference of controller button config, PARAM_CTRL_BUTTON | |
90 | * flag. | |
91 | * @static_ip_addr: User preference of static IP address. | |
92 | * @network_mask: User preference of static network mask. | |
93 | * @default_gateway: User preference of static default gateway. | |
94 | * @dns_primary: User preference of static primary dns server. | |
95 | * @dns_secondary: User preference of static secondary dns server. | |
96 | * | |
ca94297f GL |
97 | * The ps3 rtc maintains a read-only value that approximates seconds since |
98 | * 2000-01-01 00:00:00 UTC. | |
99 | * | |
00a3e2e9 GL |
100 | * User preference of zero for static_ip_addr means use dhcp. |
101 | */ | |
102 | ||
103 | struct os_area_params { | |
104 | u32 boot_flag; | |
105 | u32 _reserved_1[3]; | |
106 | u32 num_params; | |
107 | u32 _reserved_2[3]; | |
108 | /* param 0 */ | |
109 | s64 rtc_diff; | |
110 | u8 av_multi_out; | |
111 | u8 ctrl_button; | |
112 | u8 _reserved_3[6]; | |
113 | /* param 1 */ | |
114 | u8 static_ip_addr[4]; | |
115 | u8 network_mask[4]; | |
116 | u8 default_gateway[4]; | |
117 | u8 _reserved_4[4]; | |
118 | /* param 2 */ | |
119 | u8 dns_primary[4]; | |
120 | u8 dns_secondary[4]; | |
121 | u8 _reserved_5[8]; | |
a8229a9e | 122 | }; |
00a3e2e9 | 123 | |
ec5d2dfe | 124 | #define OS_AREA_DB_MAGIC_NUM "-db-" |
ef2ac63a GL |
125 | |
126 | /** | |
127 | * struct os_area_db - Shared flash memory database. | |
ec5d2dfe | 128 | * @magic_num: Always '-db-'. |
ef2ac63a GL |
129 | * @version: os_area_db format version number. |
130 | * @index_64: byte offset of the database id index for 64 bit variables. | |
131 | * @count_64: number of usable 64 bit index entries | |
132 | * @index_32: byte offset of the database id index for 32 bit variables. | |
133 | * @count_32: number of usable 32 bit index entries | |
134 | * @index_16: byte offset of the database id index for 16 bit variables. | |
135 | * @count_16: number of usable 16 bit index entries | |
136 | * | |
137 | * Flash rom storage for exclusive use by guests running in the other os lpar. | |
138 | * The current system configuration allocates 1K (two segments) for other os | |
139 | * use. | |
140 | */ | |
141 | ||
142 | struct os_area_db { | |
ec5d2dfe | 143 | u8 magic_num[4]; |
ef2ac63a GL |
144 | u16 version; |
145 | u16 _reserved_1; | |
146 | u16 index_64; | |
147 | u16 count_64; | |
148 | u16 index_32; | |
149 | u16 count_32; | |
150 | u16 index_16; | |
151 | u16 count_16; | |
152 | u32 _reserved_2; | |
153 | u8 _db_data[1000]; | |
154 | }; | |
155 | ||
156 | /** | |
157 | * enum os_area_db_owner - Data owners. | |
158 | */ | |
159 | ||
160 | enum os_area_db_owner { | |
161 | OS_AREA_DB_OWNER_ANY = -1, | |
162 | OS_AREA_DB_OWNER_NONE = 0, | |
163 | OS_AREA_DB_OWNER_PROTOTYPE = 1, | |
164 | OS_AREA_DB_OWNER_LINUX = 2, | |
165 | OS_AREA_DB_OWNER_PETITBOOT = 3, | |
166 | OS_AREA_DB_OWNER_MAX = 32, | |
167 | }; | |
168 | ||
169 | enum os_area_db_key { | |
170 | OS_AREA_DB_KEY_ANY = -1, | |
171 | OS_AREA_DB_KEY_NONE = 0, | |
172 | OS_AREA_DB_KEY_RTC_DIFF = 1, | |
173 | OS_AREA_DB_KEY_VIDEO_MODE = 2, | |
174 | OS_AREA_DB_KEY_MAX = 8, | |
175 | }; | |
176 | ||
177 | struct os_area_db_id { | |
178 | int owner; | |
179 | int key; | |
180 | }; | |
181 | ||
182 | static const struct os_area_db_id os_area_db_id_empty = { | |
183 | .owner = OS_AREA_DB_OWNER_NONE, | |
184 | .key = OS_AREA_DB_KEY_NONE | |
185 | }; | |
186 | ||
187 | static const struct os_area_db_id os_area_db_id_any = { | |
188 | .owner = OS_AREA_DB_OWNER_ANY, | |
189 | .key = OS_AREA_DB_KEY_ANY | |
190 | }; | |
191 | ||
192 | static const struct os_area_db_id os_area_db_id_rtc_diff = { | |
193 | .owner = OS_AREA_DB_OWNER_LINUX, | |
194 | .key = OS_AREA_DB_KEY_RTC_DIFF | |
195 | }; | |
196 | ||
ca94297f GL |
197 | #define SECONDS_FROM_1970_TO_2000 946684800LL |
198 | ||
00a3e2e9 | 199 | /** |
01263e88 | 200 | * struct saved_params - Static working copies of data from the PS3 'os area'. |
ef2ac63a GL |
201 | * |
202 | * The order of preference we use for the rtc_diff source: | |
203 | * 1) The database value. | |
204 | * 2) The game os value. | |
205 | * 3) The number of seconds from 1970 to 2000. | |
00a3e2e9 GL |
206 | */ |
207 | ||
208 | struct saved_params { | |
7db19421 | 209 | unsigned int valid; |
00a3e2e9 GL |
210 | s64 rtc_diff; |
211 | unsigned int av_multi_out; | |
00a3e2e9 GL |
212 | } static saved_params; |
213 | ||
7db19421 GL |
214 | static struct property property_rtc_diff = { |
215 | .name = "linux,rtc_diff", | |
216 | .length = sizeof(saved_params.rtc_diff), | |
217 | .value = &saved_params.rtc_diff, | |
218 | }; | |
219 | ||
220 | static struct property property_av_multi_out = { | |
221 | .name = "linux,av_multi_out", | |
222 | .length = sizeof(saved_params.av_multi_out), | |
223 | .value = &saved_params.av_multi_out, | |
224 | }; | |
225 | ||
a4e623fb GU |
226 | |
227 | static DEFINE_MUTEX(os_area_flash_mutex); | |
228 | ||
229 | static const struct ps3_os_area_flash_ops *os_area_flash_ops; | |
230 | ||
231 | void ps3_os_area_flash_register(const struct ps3_os_area_flash_ops *ops) | |
232 | { | |
233 | mutex_lock(&os_area_flash_mutex); | |
234 | os_area_flash_ops = ops; | |
235 | mutex_unlock(&os_area_flash_mutex); | |
236 | } | |
237 | EXPORT_SYMBOL_GPL(ps3_os_area_flash_register); | |
238 | ||
239 | static ssize_t os_area_flash_read(void *buf, size_t count, loff_t pos) | |
240 | { | |
241 | ssize_t res = -ENODEV; | |
242 | ||
243 | mutex_lock(&os_area_flash_mutex); | |
244 | if (os_area_flash_ops) | |
245 | res = os_area_flash_ops->read(buf, count, pos); | |
246 | mutex_unlock(&os_area_flash_mutex); | |
247 | ||
248 | return res; | |
249 | } | |
250 | ||
251 | static ssize_t os_area_flash_write(const void *buf, size_t count, loff_t pos) | |
252 | { | |
253 | ssize_t res = -ENODEV; | |
254 | ||
255 | mutex_lock(&os_area_flash_mutex); | |
256 | if (os_area_flash_ops) | |
257 | res = os_area_flash_ops->write(buf, count, pos); | |
258 | mutex_unlock(&os_area_flash_mutex); | |
259 | ||
260 | return res; | |
261 | } | |
262 | ||
263 | ||
7db19421 GL |
264 | /** |
265 | * os_area_set_property - Add or overwrite a saved_params value to the device tree. | |
266 | * | |
267 | * Overwrites an existing property. | |
268 | */ | |
269 | ||
270 | static void os_area_set_property(struct device_node *node, | |
271 | struct property *prop) | |
272 | { | |
273 | int result; | |
274 | struct property *tmp = of_find_property(node, prop->name, NULL); | |
275 | ||
276 | if (tmp) { | |
277 | pr_debug("%s:%d found %s\n", __func__, __LINE__, prop->name); | |
79d1c712 | 278 | of_remove_property(node, tmp); |
7db19421 GL |
279 | } |
280 | ||
79d1c712 | 281 | result = of_add_property(node, prop); |
7db19421 GL |
282 | |
283 | if (result) | |
79d1c712 | 284 | pr_debug("%s:%d of_set_property failed\n", __func__, |
7db19421 GL |
285 | __LINE__); |
286 | } | |
287 | ||
288 | /** | |
289 | * os_area_get_property - Get a saved_params value from the device tree. | |
290 | * | |
291 | */ | |
292 | ||
293 | static void __init os_area_get_property(struct device_node *node, | |
294 | struct property *prop) | |
295 | { | |
296 | const struct property *tmp = of_find_property(node, prop->name, NULL); | |
297 | ||
298 | if (tmp) { | |
299 | BUG_ON(prop->length != tmp->length); | |
300 | memcpy(prop->value, tmp->value, prop->length); | |
301 | } else | |
302 | pr_debug("%s:%d not found %s\n", __func__, __LINE__, | |
303 | prop->name); | |
304 | } | |
305 | ||
ec5d2dfe GL |
306 | static void dump_field(char *s, const u8 *field, int size_of_field) |
307 | { | |
308 | #if defined(DEBUG) | |
309 | int i; | |
310 | ||
311 | for (i = 0; i < size_of_field; i++) | |
312 | s[i] = isprint(field[i]) ? field[i] : '.'; | |
313 | s[i] = 0; | |
314 | #endif | |
315 | } | |
316 | ||
00a3e2e9 | 317 | #define dump_header(_a) _dump_header(_a, __func__, __LINE__) |
670ad354 | 318 | static void _dump_header(const struct os_area_header *h, const char *func, |
00a3e2e9 GL |
319 | int line) |
320 | { | |
ec5d2dfe GL |
321 | char str[sizeof(h->magic_num) + 1]; |
322 | ||
323 | dump_field(str, h->magic_num, sizeof(h->magic_num)); | |
ef2ac63a | 324 | pr_debug("%s:%d: h.magic_num: '%s'\n", func, line, |
ec5d2dfe | 325 | str); |
ef2ac63a | 326 | pr_debug("%s:%d: h.hdr_version: %u\n", func, line, |
00a3e2e9 | 327 | h->hdr_version); |
ef2ac63a GL |
328 | pr_debug("%s:%d: h.db_area_offset: %u\n", func, line, |
329 | h->db_area_offset); | |
00a3e2e9 GL |
330 | pr_debug("%s:%d: h.ldr_area_offset: %u\n", func, line, |
331 | h->ldr_area_offset); | |
ef2ac63a | 332 | pr_debug("%s:%d: h.ldr_format: %u\n", func, line, |
00a3e2e9 | 333 | h->ldr_format); |
ef2ac63a | 334 | pr_debug("%s:%d: h.ldr_size: %xh\n", func, line, |
00a3e2e9 GL |
335 | h->ldr_size); |
336 | } | |
337 | ||
338 | #define dump_params(_a) _dump_params(_a, __func__, __LINE__) | |
670ad354 | 339 | static void _dump_params(const struct os_area_params *p, const char *func, |
00a3e2e9 GL |
340 | int line) |
341 | { | |
342 | pr_debug("%s:%d: p.boot_flag: %u\n", func, line, p->boot_flag); | |
343 | pr_debug("%s:%d: p.num_params: %u\n", func, line, p->num_params); | |
5c949070 | 344 | pr_debug("%s:%d: p.rtc_diff %lld\n", func, line, p->rtc_diff); |
00a3e2e9 GL |
345 | pr_debug("%s:%d: p.av_multi_out %u\n", func, line, p->av_multi_out); |
346 | pr_debug("%s:%d: p.ctrl_button: %u\n", func, line, p->ctrl_button); | |
347 | pr_debug("%s:%d: p.static_ip_addr: %u.%u.%u.%u\n", func, line, | |
348 | p->static_ip_addr[0], p->static_ip_addr[1], | |
349 | p->static_ip_addr[2], p->static_ip_addr[3]); | |
350 | pr_debug("%s:%d: p.network_mask: %u.%u.%u.%u\n", func, line, | |
351 | p->network_mask[0], p->network_mask[1], | |
352 | p->network_mask[2], p->network_mask[3]); | |
353 | pr_debug("%s:%d: p.default_gateway: %u.%u.%u.%u\n", func, line, | |
354 | p->default_gateway[0], p->default_gateway[1], | |
355 | p->default_gateway[2], p->default_gateway[3]); | |
356 | pr_debug("%s:%d: p.dns_primary: %u.%u.%u.%u\n", func, line, | |
357 | p->dns_primary[0], p->dns_primary[1], | |
358 | p->dns_primary[2], p->dns_primary[3]); | |
359 | pr_debug("%s:%d: p.dns_secondary: %u.%u.%u.%u\n", func, line, | |
360 | p->dns_secondary[0], p->dns_secondary[1], | |
361 | p->dns_secondary[2], p->dns_secondary[3]); | |
362 | } | |
363 | ||
ef2ac63a | 364 | static int verify_header(const struct os_area_header *header) |
00a3e2e9 | 365 | { |
ec5d2dfe GL |
366 | if (memcmp(header->magic_num, OS_AREA_HEADER_MAGIC_NUM, |
367 | sizeof(header->magic_num))) { | |
00a3e2e9 GL |
368 | pr_debug("%s:%d magic_num failed\n", __func__, __LINE__); |
369 | return -1; | |
370 | } | |
371 | ||
372 | if (header->hdr_version < 1) { | |
373 | pr_debug("%s:%d hdr_version failed\n", __func__, __LINE__); | |
374 | return -1; | |
375 | } | |
376 | ||
ef2ac63a | 377 | if (header->db_area_offset > header->ldr_area_offset) { |
00a3e2e9 GL |
378 | pr_debug("%s:%d offsets failed\n", __func__, __LINE__); |
379 | return -1; | |
380 | } | |
381 | ||
382 | return 0; | |
383 | } | |
384 | ||
ef2ac63a GL |
385 | static int db_verify(const struct os_area_db *db) |
386 | { | |
ec5d2dfe GL |
387 | if (memcmp(db->magic_num, OS_AREA_DB_MAGIC_NUM, |
388 | sizeof(db->magic_num))) { | |
ef2ac63a | 389 | pr_debug("%s:%d magic_num failed\n", __func__, __LINE__); |
a4e623fb | 390 | return -EINVAL; |
ef2ac63a GL |
391 | } |
392 | ||
393 | if (db->version != 1) { | |
394 | pr_debug("%s:%d version failed\n", __func__, __LINE__); | |
a4e623fb | 395 | return -EINVAL; |
ef2ac63a GL |
396 | } |
397 | ||
398 | return 0; | |
399 | } | |
400 | ||
401 | struct db_index { | |
402 | uint8_t owner:5; | |
403 | uint8_t key:3; | |
404 | }; | |
405 | ||
406 | struct db_iterator { | |
407 | const struct os_area_db *db; | |
408 | struct os_area_db_id match_id; | |
409 | struct db_index *idx; | |
410 | struct db_index *last_idx; | |
411 | union { | |
412 | uint64_t *value_64; | |
413 | uint32_t *value_32; | |
414 | uint16_t *value_16; | |
415 | }; | |
416 | }; | |
417 | ||
418 | static unsigned int db_align_up(unsigned int val, unsigned int size) | |
419 | { | |
420 | return (val + (size - 1)) & (~(size - 1)); | |
421 | } | |
422 | ||
423 | /** | |
424 | * db_for_each_64 - Iterator for 64 bit entries. | |
425 | * | |
426 | * A NULL value for id can be used to match all entries. | |
427 | * OS_AREA_DB_OWNER_ANY and OS_AREA_DB_KEY_ANY can be used to match all. | |
428 | */ | |
429 | ||
430 | static int db_for_each_64(const struct os_area_db *db, | |
431 | const struct os_area_db_id *match_id, struct db_iterator *i) | |
432 | { | |
433 | next: | |
434 | if (!i->db) { | |
435 | i->db = db; | |
436 | i->match_id = match_id ? *match_id : os_area_db_id_any; | |
437 | i->idx = (void *)db + db->index_64; | |
438 | i->last_idx = i->idx + db->count_64; | |
439 | i->value_64 = (void *)db + db->index_64 | |
440 | + db_align_up(db->count_64, 8); | |
441 | } else { | |
442 | i->idx++; | |
443 | i->value_64++; | |
444 | } | |
445 | ||
446 | if (i->idx >= i->last_idx) { | |
447 | pr_debug("%s:%d: reached end\n", __func__, __LINE__); | |
448 | return 0; | |
449 | } | |
450 | ||
451 | if (i->match_id.owner != OS_AREA_DB_OWNER_ANY | |
452 | && i->match_id.owner != (int)i->idx->owner) | |
453 | goto next; | |
454 | if (i->match_id.key != OS_AREA_DB_KEY_ANY | |
455 | && i->match_id.key != (int)i->idx->key) | |
456 | goto next; | |
457 | ||
458 | return 1; | |
459 | } | |
460 | ||
461 | static int db_delete_64(struct os_area_db *db, const struct os_area_db_id *id) | |
462 | { | |
463 | struct db_iterator i; | |
464 | ||
465 | for (i.db = NULL; db_for_each_64(db, id, &i); ) { | |
466 | ||
467 | pr_debug("%s:%d: got (%d:%d) %llxh\n", __func__, __LINE__, | |
468 | i.idx->owner, i.idx->key, | |
469 | (unsigned long long)*i.value_64); | |
470 | ||
471 | i.idx->owner = 0; | |
472 | i.idx->key = 0; | |
473 | *i.value_64 = 0; | |
474 | } | |
475 | return 0; | |
476 | } | |
477 | ||
478 | static int db_set_64(struct os_area_db *db, const struct os_area_db_id *id, | |
479 | uint64_t value) | |
480 | { | |
481 | struct db_iterator i; | |
482 | ||
483 | pr_debug("%s:%d: (%d:%d) <= %llxh\n", __func__, __LINE__, | |
484 | id->owner, id->key, (unsigned long long)value); | |
485 | ||
486 | if (!id->owner || id->owner == OS_AREA_DB_OWNER_ANY | |
487 | || id->key == OS_AREA_DB_KEY_ANY) { | |
488 | pr_debug("%s:%d: bad id: (%d:%d)\n", __func__, | |
489 | __LINE__, id->owner, id->key); | |
490 | return -1; | |
491 | } | |
492 | ||
493 | db_delete_64(db, id); | |
494 | ||
495 | i.db = NULL; | |
496 | if (db_for_each_64(db, &os_area_db_id_empty, &i)) { | |
497 | ||
498 | pr_debug("%s:%d: got (%d:%d) %llxh\n", __func__, __LINE__, | |
499 | i.idx->owner, i.idx->key, | |
500 | (unsigned long long)*i.value_64); | |
501 | ||
502 | i.idx->owner = id->owner; | |
503 | i.idx->key = id->key; | |
504 | *i.value_64 = value; | |
505 | ||
506 | pr_debug("%s:%d: set (%d:%d) <= %llxh\n", __func__, __LINE__, | |
507 | i.idx->owner, i.idx->key, | |
508 | (unsigned long long)*i.value_64); | |
509 | return 0; | |
510 | } | |
511 | pr_debug("%s:%d: database full.\n", | |
512 | __func__, __LINE__); | |
513 | return -1; | |
514 | } | |
515 | ||
516 | static int db_get_64(const struct os_area_db *db, | |
517 | const struct os_area_db_id *id, uint64_t *value) | |
518 | { | |
519 | struct db_iterator i; | |
520 | ||
521 | i.db = NULL; | |
522 | if (db_for_each_64(db, id, &i)) { | |
523 | *value = *i.value_64; | |
524 | pr_debug("%s:%d: found %lld\n", __func__, __LINE__, | |
525 | (long long int)*i.value_64); | |
526 | return 0; | |
527 | } | |
528 | pr_debug("%s:%d: not found\n", __func__, __LINE__); | |
529 | return -1; | |
530 | } | |
531 | ||
532 | static int db_get_rtc_diff(const struct os_area_db *db, int64_t *rtc_diff) | |
533 | { | |
534 | return db_get_64(db, &os_area_db_id_rtc_diff, (uint64_t*)rtc_diff); | |
535 | } | |
536 | ||
537 | #define dump_db(a) _dump_db(a, __func__, __LINE__) | |
538 | static void _dump_db(const struct os_area_db *db, const char *func, | |
539 | int line) | |
540 | { | |
ec5d2dfe GL |
541 | char str[sizeof(db->magic_num) + 1]; |
542 | ||
543 | dump_field(str, db->magic_num, sizeof(db->magic_num)); | |
ef2ac63a | 544 | pr_debug("%s:%d: db.magic_num: '%s'\n", func, line, |
ec5d2dfe | 545 | str); |
ef2ac63a GL |
546 | pr_debug("%s:%d: db.version: %u\n", func, line, |
547 | db->version); | |
548 | pr_debug("%s:%d: db.index_64: %u\n", func, line, | |
549 | db->index_64); | |
550 | pr_debug("%s:%d: db.count_64: %u\n", func, line, | |
551 | db->count_64); | |
552 | pr_debug("%s:%d: db.index_32: %u\n", func, line, | |
553 | db->index_32); | |
554 | pr_debug("%s:%d: db.count_32: %u\n", func, line, | |
555 | db->count_32); | |
556 | pr_debug("%s:%d: db.index_16: %u\n", func, line, | |
557 | db->index_16); | |
558 | pr_debug("%s:%d: db.count_16: %u\n", func, line, | |
559 | db->count_16); | |
560 | } | |
561 | ||
562 | static void os_area_db_init(struct os_area_db *db) | |
563 | { | |
564 | enum { | |
565 | HEADER_SIZE = offsetof(struct os_area_db, _db_data), | |
566 | INDEX_64_COUNT = 64, | |
567 | VALUES_64_COUNT = 57, | |
568 | INDEX_32_COUNT = 64, | |
569 | VALUES_32_COUNT = 57, | |
570 | INDEX_16_COUNT = 64, | |
571 | VALUES_16_COUNT = 57, | |
572 | }; | |
573 | ||
574 | memset(db, 0, sizeof(struct os_area_db)); | |
575 | ||
ec5d2dfe | 576 | memcpy(db->magic_num, OS_AREA_DB_MAGIC_NUM, sizeof(db->magic_num)); |
ef2ac63a GL |
577 | db->version = 1; |
578 | db->index_64 = HEADER_SIZE; | |
579 | db->count_64 = VALUES_64_COUNT; | |
580 | db->index_32 = HEADER_SIZE | |
581 | + INDEX_64_COUNT * sizeof(struct db_index) | |
582 | + VALUES_64_COUNT * sizeof(u64); | |
583 | db->count_32 = VALUES_32_COUNT; | |
584 | db->index_16 = HEADER_SIZE | |
585 | + INDEX_64_COUNT * sizeof(struct db_index) | |
586 | + VALUES_64_COUNT * sizeof(u64) | |
587 | + INDEX_32_COUNT * sizeof(struct db_index) | |
588 | + VALUES_32_COUNT * sizeof(u32); | |
589 | db->count_16 = VALUES_16_COUNT; | |
590 | ||
591 | /* Rules to check db layout. */ | |
592 | ||
593 | BUILD_BUG_ON(sizeof(struct db_index) != 1); | |
594 | BUILD_BUG_ON(sizeof(struct os_area_db) != 2 * OS_AREA_SEGMENT_SIZE); | |
595 | BUILD_BUG_ON(INDEX_64_COUNT & 0x7); | |
596 | BUILD_BUG_ON(VALUES_64_COUNT > INDEX_64_COUNT); | |
597 | BUILD_BUG_ON(INDEX_32_COUNT & 0x7); | |
598 | BUILD_BUG_ON(VALUES_32_COUNT > INDEX_32_COUNT); | |
599 | BUILD_BUG_ON(INDEX_16_COUNT & 0x7); | |
600 | BUILD_BUG_ON(VALUES_16_COUNT > INDEX_16_COUNT); | |
601 | BUILD_BUG_ON(HEADER_SIZE | |
602 | + INDEX_64_COUNT * sizeof(struct db_index) | |
603 | + VALUES_64_COUNT * sizeof(u64) | |
604 | + INDEX_32_COUNT * sizeof(struct db_index) | |
605 | + VALUES_32_COUNT * sizeof(u32) | |
606 | + INDEX_16_COUNT * sizeof(struct db_index) | |
607 | + VALUES_16_COUNT * sizeof(u16) | |
608 | > sizeof(struct os_area_db)); | |
609 | } | |
610 | ||
611 | /** | |
612 | * update_flash_db - Helper for os_area_queue_work_handler. | |
613 | * | |
614 | */ | |
615 | ||
a4e623fb | 616 | static int update_flash_db(void) |
ef2ac63a | 617 | { |
a4e623fb GU |
618 | const unsigned int buf_len = 8 * OS_AREA_SEGMENT_SIZE; |
619 | struct os_area_header *header; | |
ef2ac63a | 620 | ssize_t count; |
a4e623fb GU |
621 | int error; |
622 | loff_t pos; | |
ef2ac63a GL |
623 | struct os_area_db* db; |
624 | ||
625 | /* Read in header and db from flash. */ | |
626 | ||
ef2ac63a | 627 | header = kmalloc(buf_len, GFP_KERNEL); |
ef2ac63a | 628 | if (!header) { |
a4e623fb GU |
629 | pr_debug("%s: kmalloc failed\n", __func__); |
630 | return -ENOMEM; | |
ef2ac63a GL |
631 | } |
632 | ||
a4e623fb GU |
633 | count = os_area_flash_read(header, buf_len, 0); |
634 | if (count < 0) { | |
635 | pr_debug("%s: os_area_flash_read failed %zd\n", __func__, | |
636 | count); | |
637 | error = count; | |
638 | goto fail; | |
ef2ac63a GL |
639 | } |
640 | ||
a4e623fb GU |
641 | pos = header->db_area_offset * OS_AREA_SEGMENT_SIZE; |
642 | if (count < OS_AREA_SEGMENT_SIZE || verify_header(header) || | |
643 | count < pos) { | |
644 | pr_debug("%s: verify_header failed\n", __func__); | |
ef2ac63a | 645 | dump_header(header); |
a4e623fb GU |
646 | error = -EINVAL; |
647 | goto fail; | |
ef2ac63a GL |
648 | } |
649 | ||
650 | /* Now got a good db offset and some maybe good db data. */ | |
651 | ||
a4e623fb | 652 | db = (void *)header + pos; |
ef2ac63a | 653 | |
a4e623fb GU |
654 | error = db_verify(db); |
655 | if (error) { | |
656 | pr_notice("%s: Verify of flash database failed, formatting.\n", | |
657 | __func__); | |
ef2ac63a GL |
658 | dump_db(db); |
659 | os_area_db_init(db); | |
660 | } | |
661 | ||
662 | /* Now got good db data. */ | |
663 | ||
664 | db_set_64(db, &os_area_db_id_rtc_diff, saved_params.rtc_diff); | |
665 | ||
a4e623fb | 666 | count = os_area_flash_write(db, sizeof(struct os_area_db), pos); |
ef2ac63a | 667 | if (count < sizeof(struct os_area_db)) { |
a4e623fb GU |
668 | pr_debug("%s: os_area_flash_write failed %zd\n", __func__, |
669 | count); | |
670 | error = count < 0 ? count : -EIO; | |
ef2ac63a GL |
671 | } |
672 | ||
a4e623fb | 673 | fail: |
ef2ac63a | 674 | kfree(header); |
a4e623fb | 675 | return error; |
ef2ac63a GL |
676 | } |
677 | ||
418ef209 GL |
678 | /** |
679 | * os_area_queue_work_handler - Asynchronous write handler. | |
680 | * | |
681 | * An asynchronous write for flash memory and the device tree. Do not | |
682 | * call directly, use os_area_queue_work(). | |
683 | */ | |
684 | ||
685 | static void os_area_queue_work_handler(struct work_struct *work) | |
686 | { | |
7db19421 | 687 | struct device_node *node; |
a4e623fb | 688 | int error; |
7db19421 | 689 | |
418ef209 GL |
690 | pr_debug(" -> %s:%d\n", __func__, __LINE__); |
691 | ||
7db19421 | 692 | node = of_find_node_by_path("/"); |
7db19421 GL |
693 | if (node) { |
694 | os_area_set_property(node, &property_rtc_diff); | |
695 | of_node_put(node); | |
696 | } else | |
697 | pr_debug("%s:%d of_find_node_by_path failed\n", | |
698 | __func__, __LINE__); | |
699 | ||
a4e623fb GU |
700 | error = update_flash_db(); |
701 | if (error) | |
702 | pr_warning("%s: Could not update FLASH ROM\n", __func__); | |
703 | ||
418ef209 GL |
704 | pr_debug(" <- %s:%d\n", __func__, __LINE__); |
705 | } | |
706 | ||
707 | static void os_area_queue_work(void) | |
708 | { | |
709 | static DECLARE_WORK(q, os_area_queue_work_handler); | |
710 | ||
711 | wmb(); | |
712 | schedule_work(&q); | |
713 | } | |
714 | ||
01263e88 GL |
715 | /** |
716 | * ps3_os_area_save_params - Copy data from os area mirror to @saved_params. | |
717 | * | |
ef2ac63a | 718 | * For the convenience of the guest the HV makes a copy of the os area in |
01263e88 | 719 | * flash to a high address in the boot memory region and then puts that RAM |
ef2ac63a | 720 | * address and the byte count into the repository for retrieval by the guest. |
01263e88 | 721 | * We copy the data we want into a static variable and allow the memory setup |
95f72d1e | 722 | * by the HV to be claimed by the memblock manager. |
ef2ac63a GL |
723 | * |
724 | * The os area mirror will not be available to a second stage kernel, and | |
725 | * the header verify will fail. In this case, the saved_params values will | |
726 | * be set from flash memory or the passed in device tree in ps3_os_area_init(). | |
01263e88 GL |
727 | */ |
728 | ||
729 | void __init ps3_os_area_save_params(void) | |
00a3e2e9 GL |
730 | { |
731 | int result; | |
732 | u64 lpar_addr; | |
733 | unsigned int size; | |
734 | struct os_area_header *header; | |
735 | struct os_area_params *params; | |
ef2ac63a | 736 | struct os_area_db *db; |
00a3e2e9 | 737 | |
01263e88 GL |
738 | pr_debug(" -> %s:%d\n", __func__, __LINE__); |
739 | ||
00a3e2e9 GL |
740 | result = ps3_repository_read_boot_dat_info(&lpar_addr, &size); |
741 | ||
742 | if (result) { | |
743 | pr_debug("%s:%d ps3_repository_read_boot_dat_info failed\n", | |
744 | __func__, __LINE__); | |
01263e88 | 745 | return; |
00a3e2e9 GL |
746 | } |
747 | ||
748 | header = (struct os_area_header *)__va(lpar_addr); | |
ca94297f GL |
749 | params = (struct os_area_params *)__va(lpar_addr |
750 | + OS_AREA_SEGMENT_SIZE); | |
00a3e2e9 GL |
751 | |
752 | result = verify_header(header); | |
753 | ||
754 | if (result) { | |
7db19421 | 755 | /* Second stage kernels exit here. */ |
00a3e2e9 GL |
756 | pr_debug("%s:%d verify_header failed\n", __func__, __LINE__); |
757 | dump_header(header); | |
01263e88 | 758 | return; |
00a3e2e9 GL |
759 | } |
760 | ||
ef2ac63a GL |
761 | db = (struct os_area_db *)__va(lpar_addr |
762 | + header->db_area_offset * OS_AREA_SEGMENT_SIZE); | |
763 | ||
00a3e2e9 GL |
764 | dump_header(header); |
765 | dump_params(params); | |
ef2ac63a | 766 | dump_db(db); |
00a3e2e9 | 767 | |
ef2ac63a GL |
768 | result = db_verify(db) || db_get_rtc_diff(db, &saved_params.rtc_diff); |
769 | if (result) | |
770 | saved_params.rtc_diff = params->rtc_diff ? params->rtc_diff | |
771 | : SECONDS_FROM_1970_TO_2000; | |
00a3e2e9 | 772 | saved_params.av_multi_out = params->av_multi_out; |
7db19421 | 773 | saved_params.valid = 1; |
00a3e2e9 | 774 | |
01263e88 GL |
775 | memset(header, 0, sizeof(*header)); |
776 | ||
777 | pr_debug(" <- %s:%d\n", __func__, __LINE__); | |
00a3e2e9 GL |
778 | } |
779 | ||
7db19421 GL |
780 | /** |
781 | * ps3_os_area_init - Setup os area device tree properties as needed. | |
782 | */ | |
783 | ||
784 | void __init ps3_os_area_init(void) | |
785 | { | |
786 | struct device_node *node; | |
787 | ||
788 | pr_debug(" -> %s:%d\n", __func__, __LINE__); | |
789 | ||
790 | node = of_find_node_by_path("/"); | |
791 | ||
792 | if (!saved_params.valid && node) { | |
793 | /* Second stage kernels should have a dt entry. */ | |
794 | os_area_get_property(node, &property_rtc_diff); | |
795 | os_area_get_property(node, &property_av_multi_out); | |
796 | } | |
797 | ||
798 | if(!saved_params.rtc_diff) | |
799 | saved_params.rtc_diff = SECONDS_FROM_1970_TO_2000; | |
800 | ||
801 | if (node) { | |
802 | os_area_set_property(node, &property_rtc_diff); | |
803 | os_area_set_property(node, &property_av_multi_out); | |
804 | of_node_put(node); | |
805 | } else | |
806 | pr_debug("%s:%d of_find_node_by_path failed\n", | |
807 | __func__, __LINE__); | |
808 | ||
809 | pr_debug(" <- %s:%d\n", __func__, __LINE__); | |
810 | } | |
811 | ||
00a3e2e9 | 812 | /** |
d7b98e3d | 813 | * ps3_os_area_get_rtc_diff - Returns the rtc diff value. |
00a3e2e9 GL |
814 | */ |
815 | ||
d7b98e3d | 816 | u64 ps3_os_area_get_rtc_diff(void) |
00a3e2e9 | 817 | { |
7db19421 | 818 | return saved_params.rtc_diff; |
00a3e2e9 | 819 | } |
47cb996b | 820 | EXPORT_SYMBOL_GPL(ps3_os_area_get_rtc_diff); |
098e2744 | 821 | |
d7b98e3d GL |
822 | /** |
823 | * ps3_os_area_set_rtc_diff - Set the rtc diff value. | |
824 | * | |
825 | * An asynchronous write is needed to support writing updates from | |
826 | * the timer interrupt context. | |
827 | */ | |
828 | ||
829 | void ps3_os_area_set_rtc_diff(u64 rtc_diff) | |
830 | { | |
831 | if (saved_params.rtc_diff != rtc_diff) { | |
832 | saved_params.rtc_diff = rtc_diff; | |
833 | os_area_queue_work(); | |
834 | } | |
835 | } | |
47cb996b | 836 | EXPORT_SYMBOL_GPL(ps3_os_area_set_rtc_diff); |
d7b98e3d | 837 | |
098e2744 GU |
838 | /** |
839 | * ps3_os_area_get_av_multi_out - Returns the default video mode. | |
840 | */ | |
841 | ||
842 | enum ps3_param_av_multi_out ps3_os_area_get_av_multi_out(void) | |
843 | { | |
844 | return saved_params.av_multi_out; | |
845 | } | |
846 | EXPORT_SYMBOL_GPL(ps3_os_area_get_av_multi_out); |