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 | ||
197 | static const struct os_area_db_id os_area_db_id_video_mode = { | |
198 | .owner = OS_AREA_DB_OWNER_LINUX, | |
199 | .key = OS_AREA_DB_KEY_VIDEO_MODE | |
200 | }; | |
201 | ||
ca94297f GL |
202 | #define SECONDS_FROM_1970_TO_2000 946684800LL |
203 | ||
00a3e2e9 | 204 | /** |
01263e88 | 205 | * struct saved_params - Static working copies of data from the PS3 'os area'. |
ef2ac63a GL |
206 | * |
207 | * The order of preference we use for the rtc_diff source: | |
208 | * 1) The database value. | |
209 | * 2) The game os value. | |
210 | * 3) The number of seconds from 1970 to 2000. | |
00a3e2e9 GL |
211 | */ |
212 | ||
213 | struct saved_params { | |
7db19421 | 214 | unsigned int valid; |
00a3e2e9 GL |
215 | s64 rtc_diff; |
216 | unsigned int av_multi_out; | |
00a3e2e9 GL |
217 | } static saved_params; |
218 | ||
7db19421 GL |
219 | static struct property property_rtc_diff = { |
220 | .name = "linux,rtc_diff", | |
221 | .length = sizeof(saved_params.rtc_diff), | |
222 | .value = &saved_params.rtc_diff, | |
223 | }; | |
224 | ||
225 | static struct property property_av_multi_out = { | |
226 | .name = "linux,av_multi_out", | |
227 | .length = sizeof(saved_params.av_multi_out), | |
228 | .value = &saved_params.av_multi_out, | |
229 | }; | |
230 | ||
a4e623fb GU |
231 | |
232 | static DEFINE_MUTEX(os_area_flash_mutex); | |
233 | ||
234 | static const struct ps3_os_area_flash_ops *os_area_flash_ops; | |
235 | ||
236 | void ps3_os_area_flash_register(const struct ps3_os_area_flash_ops *ops) | |
237 | { | |
238 | mutex_lock(&os_area_flash_mutex); | |
239 | os_area_flash_ops = ops; | |
240 | mutex_unlock(&os_area_flash_mutex); | |
241 | } | |
242 | EXPORT_SYMBOL_GPL(ps3_os_area_flash_register); | |
243 | ||
244 | static ssize_t os_area_flash_read(void *buf, size_t count, loff_t pos) | |
245 | { | |
246 | ssize_t res = -ENODEV; | |
247 | ||
248 | mutex_lock(&os_area_flash_mutex); | |
249 | if (os_area_flash_ops) | |
250 | res = os_area_flash_ops->read(buf, count, pos); | |
251 | mutex_unlock(&os_area_flash_mutex); | |
252 | ||
253 | return res; | |
254 | } | |
255 | ||
256 | static ssize_t os_area_flash_write(const void *buf, size_t count, loff_t pos) | |
257 | { | |
258 | ssize_t res = -ENODEV; | |
259 | ||
260 | mutex_lock(&os_area_flash_mutex); | |
261 | if (os_area_flash_ops) | |
262 | res = os_area_flash_ops->write(buf, count, pos); | |
263 | mutex_unlock(&os_area_flash_mutex); | |
264 | ||
265 | return res; | |
266 | } | |
267 | ||
268 | ||
7db19421 GL |
269 | /** |
270 | * os_area_set_property - Add or overwrite a saved_params value to the device tree. | |
271 | * | |
272 | * Overwrites an existing property. | |
273 | */ | |
274 | ||
275 | static void os_area_set_property(struct device_node *node, | |
276 | struct property *prop) | |
277 | { | |
278 | int result; | |
279 | struct property *tmp = of_find_property(node, prop->name, NULL); | |
280 | ||
281 | if (tmp) { | |
282 | pr_debug("%s:%d found %s\n", __func__, __LINE__, prop->name); | |
79d1c712 | 283 | of_remove_property(node, tmp); |
7db19421 GL |
284 | } |
285 | ||
79d1c712 | 286 | result = of_add_property(node, prop); |
7db19421 GL |
287 | |
288 | if (result) | |
79d1c712 | 289 | pr_debug("%s:%d of_set_property failed\n", __func__, |
7db19421 GL |
290 | __LINE__); |
291 | } | |
292 | ||
293 | /** | |
294 | * os_area_get_property - Get a saved_params value from the device tree. | |
295 | * | |
296 | */ | |
297 | ||
298 | static void __init os_area_get_property(struct device_node *node, | |
299 | struct property *prop) | |
300 | { | |
301 | const struct property *tmp = of_find_property(node, prop->name, NULL); | |
302 | ||
303 | if (tmp) { | |
304 | BUG_ON(prop->length != tmp->length); | |
305 | memcpy(prop->value, tmp->value, prop->length); | |
306 | } else | |
307 | pr_debug("%s:%d not found %s\n", __func__, __LINE__, | |
308 | prop->name); | |
309 | } | |
310 | ||
ec5d2dfe GL |
311 | static void dump_field(char *s, const u8 *field, int size_of_field) |
312 | { | |
313 | #if defined(DEBUG) | |
314 | int i; | |
315 | ||
316 | for (i = 0; i < size_of_field; i++) | |
317 | s[i] = isprint(field[i]) ? field[i] : '.'; | |
318 | s[i] = 0; | |
319 | #endif | |
320 | } | |
321 | ||
00a3e2e9 | 322 | #define dump_header(_a) _dump_header(_a, __func__, __LINE__) |
670ad354 | 323 | static void _dump_header(const struct os_area_header *h, const char *func, |
00a3e2e9 GL |
324 | int line) |
325 | { | |
ec5d2dfe GL |
326 | char str[sizeof(h->magic_num) + 1]; |
327 | ||
328 | dump_field(str, h->magic_num, sizeof(h->magic_num)); | |
ef2ac63a | 329 | pr_debug("%s:%d: h.magic_num: '%s'\n", func, line, |
ec5d2dfe | 330 | str); |
ef2ac63a | 331 | pr_debug("%s:%d: h.hdr_version: %u\n", func, line, |
00a3e2e9 | 332 | h->hdr_version); |
ef2ac63a GL |
333 | pr_debug("%s:%d: h.db_area_offset: %u\n", func, line, |
334 | h->db_area_offset); | |
00a3e2e9 GL |
335 | pr_debug("%s:%d: h.ldr_area_offset: %u\n", func, line, |
336 | h->ldr_area_offset); | |
ef2ac63a | 337 | pr_debug("%s:%d: h.ldr_format: %u\n", func, line, |
00a3e2e9 | 338 | h->ldr_format); |
ef2ac63a | 339 | pr_debug("%s:%d: h.ldr_size: %xh\n", func, line, |
00a3e2e9 GL |
340 | h->ldr_size); |
341 | } | |
342 | ||
343 | #define dump_params(_a) _dump_params(_a, __func__, __LINE__) | |
670ad354 | 344 | static void _dump_params(const struct os_area_params *p, const char *func, |
00a3e2e9 GL |
345 | int line) |
346 | { | |
347 | pr_debug("%s:%d: p.boot_flag: %u\n", func, line, p->boot_flag); | |
348 | pr_debug("%s:%d: p.num_params: %u\n", func, line, p->num_params); | |
5c949070 | 349 | pr_debug("%s:%d: p.rtc_diff %lld\n", func, line, p->rtc_diff); |
00a3e2e9 GL |
350 | pr_debug("%s:%d: p.av_multi_out %u\n", func, line, p->av_multi_out); |
351 | pr_debug("%s:%d: p.ctrl_button: %u\n", func, line, p->ctrl_button); | |
352 | pr_debug("%s:%d: p.static_ip_addr: %u.%u.%u.%u\n", func, line, | |
353 | p->static_ip_addr[0], p->static_ip_addr[1], | |
354 | p->static_ip_addr[2], p->static_ip_addr[3]); | |
355 | pr_debug("%s:%d: p.network_mask: %u.%u.%u.%u\n", func, line, | |
356 | p->network_mask[0], p->network_mask[1], | |
357 | p->network_mask[2], p->network_mask[3]); | |
358 | pr_debug("%s:%d: p.default_gateway: %u.%u.%u.%u\n", func, line, | |
359 | p->default_gateway[0], p->default_gateway[1], | |
360 | p->default_gateway[2], p->default_gateway[3]); | |
361 | pr_debug("%s:%d: p.dns_primary: %u.%u.%u.%u\n", func, line, | |
362 | p->dns_primary[0], p->dns_primary[1], | |
363 | p->dns_primary[2], p->dns_primary[3]); | |
364 | pr_debug("%s:%d: p.dns_secondary: %u.%u.%u.%u\n", func, line, | |
365 | p->dns_secondary[0], p->dns_secondary[1], | |
366 | p->dns_secondary[2], p->dns_secondary[3]); | |
367 | } | |
368 | ||
ef2ac63a | 369 | static int verify_header(const struct os_area_header *header) |
00a3e2e9 | 370 | { |
ec5d2dfe GL |
371 | if (memcmp(header->magic_num, OS_AREA_HEADER_MAGIC_NUM, |
372 | sizeof(header->magic_num))) { | |
00a3e2e9 GL |
373 | pr_debug("%s:%d magic_num failed\n", __func__, __LINE__); |
374 | return -1; | |
375 | } | |
376 | ||
377 | if (header->hdr_version < 1) { | |
378 | pr_debug("%s:%d hdr_version failed\n", __func__, __LINE__); | |
379 | return -1; | |
380 | } | |
381 | ||
ef2ac63a | 382 | if (header->db_area_offset > header->ldr_area_offset) { |
00a3e2e9 GL |
383 | pr_debug("%s:%d offsets failed\n", __func__, __LINE__); |
384 | return -1; | |
385 | } | |
386 | ||
387 | return 0; | |
388 | } | |
389 | ||
ef2ac63a GL |
390 | static int db_verify(const struct os_area_db *db) |
391 | { | |
ec5d2dfe GL |
392 | if (memcmp(db->magic_num, OS_AREA_DB_MAGIC_NUM, |
393 | sizeof(db->magic_num))) { | |
ef2ac63a | 394 | pr_debug("%s:%d magic_num failed\n", __func__, __LINE__); |
a4e623fb | 395 | return -EINVAL; |
ef2ac63a GL |
396 | } |
397 | ||
398 | if (db->version != 1) { | |
399 | pr_debug("%s:%d version failed\n", __func__, __LINE__); | |
a4e623fb | 400 | return -EINVAL; |
ef2ac63a GL |
401 | } |
402 | ||
403 | return 0; | |
404 | } | |
405 | ||
406 | struct db_index { | |
407 | uint8_t owner:5; | |
408 | uint8_t key:3; | |
409 | }; | |
410 | ||
411 | struct db_iterator { | |
412 | const struct os_area_db *db; | |
413 | struct os_area_db_id match_id; | |
414 | struct db_index *idx; | |
415 | struct db_index *last_idx; | |
416 | union { | |
417 | uint64_t *value_64; | |
418 | uint32_t *value_32; | |
419 | uint16_t *value_16; | |
420 | }; | |
421 | }; | |
422 | ||
423 | static unsigned int db_align_up(unsigned int val, unsigned int size) | |
424 | { | |
425 | return (val + (size - 1)) & (~(size - 1)); | |
426 | } | |
427 | ||
428 | /** | |
429 | * db_for_each_64 - Iterator for 64 bit entries. | |
430 | * | |
431 | * A NULL value for id can be used to match all entries. | |
432 | * OS_AREA_DB_OWNER_ANY and OS_AREA_DB_KEY_ANY can be used to match all. | |
433 | */ | |
434 | ||
435 | static int db_for_each_64(const struct os_area_db *db, | |
436 | const struct os_area_db_id *match_id, struct db_iterator *i) | |
437 | { | |
438 | next: | |
439 | if (!i->db) { | |
440 | i->db = db; | |
441 | i->match_id = match_id ? *match_id : os_area_db_id_any; | |
442 | i->idx = (void *)db + db->index_64; | |
443 | i->last_idx = i->idx + db->count_64; | |
444 | i->value_64 = (void *)db + db->index_64 | |
445 | + db_align_up(db->count_64, 8); | |
446 | } else { | |
447 | i->idx++; | |
448 | i->value_64++; | |
449 | } | |
450 | ||
451 | if (i->idx >= i->last_idx) { | |
452 | pr_debug("%s:%d: reached end\n", __func__, __LINE__); | |
453 | return 0; | |
454 | } | |
455 | ||
456 | if (i->match_id.owner != OS_AREA_DB_OWNER_ANY | |
457 | && i->match_id.owner != (int)i->idx->owner) | |
458 | goto next; | |
459 | if (i->match_id.key != OS_AREA_DB_KEY_ANY | |
460 | && i->match_id.key != (int)i->idx->key) | |
461 | goto next; | |
462 | ||
463 | return 1; | |
464 | } | |
465 | ||
466 | static int db_delete_64(struct os_area_db *db, const struct os_area_db_id *id) | |
467 | { | |
468 | struct db_iterator i; | |
469 | ||
470 | for (i.db = NULL; db_for_each_64(db, id, &i); ) { | |
471 | ||
472 | pr_debug("%s:%d: got (%d:%d) %llxh\n", __func__, __LINE__, | |
473 | i.idx->owner, i.idx->key, | |
474 | (unsigned long long)*i.value_64); | |
475 | ||
476 | i.idx->owner = 0; | |
477 | i.idx->key = 0; | |
478 | *i.value_64 = 0; | |
479 | } | |
480 | return 0; | |
481 | } | |
482 | ||
483 | static int db_set_64(struct os_area_db *db, const struct os_area_db_id *id, | |
484 | uint64_t value) | |
485 | { | |
486 | struct db_iterator i; | |
487 | ||
488 | pr_debug("%s:%d: (%d:%d) <= %llxh\n", __func__, __LINE__, | |
489 | id->owner, id->key, (unsigned long long)value); | |
490 | ||
491 | if (!id->owner || id->owner == OS_AREA_DB_OWNER_ANY | |
492 | || id->key == OS_AREA_DB_KEY_ANY) { | |
493 | pr_debug("%s:%d: bad id: (%d:%d)\n", __func__, | |
494 | __LINE__, id->owner, id->key); | |
495 | return -1; | |
496 | } | |
497 | ||
498 | db_delete_64(db, id); | |
499 | ||
500 | i.db = NULL; | |
501 | if (db_for_each_64(db, &os_area_db_id_empty, &i)) { | |
502 | ||
503 | pr_debug("%s:%d: got (%d:%d) %llxh\n", __func__, __LINE__, | |
504 | i.idx->owner, i.idx->key, | |
505 | (unsigned long long)*i.value_64); | |
506 | ||
507 | i.idx->owner = id->owner; | |
508 | i.idx->key = id->key; | |
509 | *i.value_64 = value; | |
510 | ||
511 | pr_debug("%s:%d: set (%d:%d) <= %llxh\n", __func__, __LINE__, | |
512 | i.idx->owner, i.idx->key, | |
513 | (unsigned long long)*i.value_64); | |
514 | return 0; | |
515 | } | |
516 | pr_debug("%s:%d: database full.\n", | |
517 | __func__, __LINE__); | |
518 | return -1; | |
519 | } | |
520 | ||
521 | static int db_get_64(const struct os_area_db *db, | |
522 | const struct os_area_db_id *id, uint64_t *value) | |
523 | { | |
524 | struct db_iterator i; | |
525 | ||
526 | i.db = NULL; | |
527 | if (db_for_each_64(db, id, &i)) { | |
528 | *value = *i.value_64; | |
529 | pr_debug("%s:%d: found %lld\n", __func__, __LINE__, | |
530 | (long long int)*i.value_64); | |
531 | return 0; | |
532 | } | |
533 | pr_debug("%s:%d: not found\n", __func__, __LINE__); | |
534 | return -1; | |
535 | } | |
536 | ||
537 | static int db_get_rtc_diff(const struct os_area_db *db, int64_t *rtc_diff) | |
538 | { | |
539 | return db_get_64(db, &os_area_db_id_rtc_diff, (uint64_t*)rtc_diff); | |
540 | } | |
541 | ||
542 | #define dump_db(a) _dump_db(a, __func__, __LINE__) | |
543 | static void _dump_db(const struct os_area_db *db, const char *func, | |
544 | int line) | |
545 | { | |
ec5d2dfe GL |
546 | char str[sizeof(db->magic_num) + 1]; |
547 | ||
548 | dump_field(str, db->magic_num, sizeof(db->magic_num)); | |
ef2ac63a | 549 | pr_debug("%s:%d: db.magic_num: '%s'\n", func, line, |
ec5d2dfe | 550 | str); |
ef2ac63a GL |
551 | pr_debug("%s:%d: db.version: %u\n", func, line, |
552 | db->version); | |
553 | pr_debug("%s:%d: db.index_64: %u\n", func, line, | |
554 | db->index_64); | |
555 | pr_debug("%s:%d: db.count_64: %u\n", func, line, | |
556 | db->count_64); | |
557 | pr_debug("%s:%d: db.index_32: %u\n", func, line, | |
558 | db->index_32); | |
559 | pr_debug("%s:%d: db.count_32: %u\n", func, line, | |
560 | db->count_32); | |
561 | pr_debug("%s:%d: db.index_16: %u\n", func, line, | |
562 | db->index_16); | |
563 | pr_debug("%s:%d: db.count_16: %u\n", func, line, | |
564 | db->count_16); | |
565 | } | |
566 | ||
567 | static void os_area_db_init(struct os_area_db *db) | |
568 | { | |
569 | enum { | |
570 | HEADER_SIZE = offsetof(struct os_area_db, _db_data), | |
571 | INDEX_64_COUNT = 64, | |
572 | VALUES_64_COUNT = 57, | |
573 | INDEX_32_COUNT = 64, | |
574 | VALUES_32_COUNT = 57, | |
575 | INDEX_16_COUNT = 64, | |
576 | VALUES_16_COUNT = 57, | |
577 | }; | |
578 | ||
579 | memset(db, 0, sizeof(struct os_area_db)); | |
580 | ||
ec5d2dfe | 581 | memcpy(db->magic_num, OS_AREA_DB_MAGIC_NUM, sizeof(db->magic_num)); |
ef2ac63a GL |
582 | db->version = 1; |
583 | db->index_64 = HEADER_SIZE; | |
584 | db->count_64 = VALUES_64_COUNT; | |
585 | db->index_32 = HEADER_SIZE | |
586 | + INDEX_64_COUNT * sizeof(struct db_index) | |
587 | + VALUES_64_COUNT * sizeof(u64); | |
588 | db->count_32 = VALUES_32_COUNT; | |
589 | db->index_16 = HEADER_SIZE | |
590 | + INDEX_64_COUNT * sizeof(struct db_index) | |
591 | + VALUES_64_COUNT * sizeof(u64) | |
592 | + INDEX_32_COUNT * sizeof(struct db_index) | |
593 | + VALUES_32_COUNT * sizeof(u32); | |
594 | db->count_16 = VALUES_16_COUNT; | |
595 | ||
596 | /* Rules to check db layout. */ | |
597 | ||
598 | BUILD_BUG_ON(sizeof(struct db_index) != 1); | |
599 | BUILD_BUG_ON(sizeof(struct os_area_db) != 2 * OS_AREA_SEGMENT_SIZE); | |
600 | BUILD_BUG_ON(INDEX_64_COUNT & 0x7); | |
601 | BUILD_BUG_ON(VALUES_64_COUNT > INDEX_64_COUNT); | |
602 | BUILD_BUG_ON(INDEX_32_COUNT & 0x7); | |
603 | BUILD_BUG_ON(VALUES_32_COUNT > INDEX_32_COUNT); | |
604 | BUILD_BUG_ON(INDEX_16_COUNT & 0x7); | |
605 | BUILD_BUG_ON(VALUES_16_COUNT > INDEX_16_COUNT); | |
606 | BUILD_BUG_ON(HEADER_SIZE | |
607 | + INDEX_64_COUNT * sizeof(struct db_index) | |
608 | + VALUES_64_COUNT * sizeof(u64) | |
609 | + INDEX_32_COUNT * sizeof(struct db_index) | |
610 | + VALUES_32_COUNT * sizeof(u32) | |
611 | + INDEX_16_COUNT * sizeof(struct db_index) | |
612 | + VALUES_16_COUNT * sizeof(u16) | |
613 | > sizeof(struct os_area_db)); | |
614 | } | |
615 | ||
616 | /** | |
617 | * update_flash_db - Helper for os_area_queue_work_handler. | |
618 | * | |
619 | */ | |
620 | ||
a4e623fb | 621 | static int update_flash_db(void) |
ef2ac63a | 622 | { |
a4e623fb GU |
623 | const unsigned int buf_len = 8 * OS_AREA_SEGMENT_SIZE; |
624 | struct os_area_header *header; | |
ef2ac63a | 625 | ssize_t count; |
a4e623fb GU |
626 | int error; |
627 | loff_t pos; | |
ef2ac63a GL |
628 | struct os_area_db* db; |
629 | ||
630 | /* Read in header and db from flash. */ | |
631 | ||
ef2ac63a | 632 | header = kmalloc(buf_len, GFP_KERNEL); |
ef2ac63a | 633 | if (!header) { |
a4e623fb GU |
634 | pr_debug("%s: kmalloc failed\n", __func__); |
635 | return -ENOMEM; | |
ef2ac63a GL |
636 | } |
637 | ||
a4e623fb GU |
638 | count = os_area_flash_read(header, buf_len, 0); |
639 | if (count < 0) { | |
640 | pr_debug("%s: os_area_flash_read failed %zd\n", __func__, | |
641 | count); | |
642 | error = count; | |
643 | goto fail; | |
ef2ac63a GL |
644 | } |
645 | ||
a4e623fb GU |
646 | pos = header->db_area_offset * OS_AREA_SEGMENT_SIZE; |
647 | if (count < OS_AREA_SEGMENT_SIZE || verify_header(header) || | |
648 | count < pos) { | |
649 | pr_debug("%s: verify_header failed\n", __func__); | |
ef2ac63a | 650 | dump_header(header); |
a4e623fb GU |
651 | error = -EINVAL; |
652 | goto fail; | |
ef2ac63a GL |
653 | } |
654 | ||
655 | /* Now got a good db offset and some maybe good db data. */ | |
656 | ||
a4e623fb | 657 | db = (void *)header + pos; |
ef2ac63a | 658 | |
a4e623fb GU |
659 | error = db_verify(db); |
660 | if (error) { | |
661 | pr_notice("%s: Verify of flash database failed, formatting.\n", | |
662 | __func__); | |
ef2ac63a GL |
663 | dump_db(db); |
664 | os_area_db_init(db); | |
665 | } | |
666 | ||
667 | /* Now got good db data. */ | |
668 | ||
669 | db_set_64(db, &os_area_db_id_rtc_diff, saved_params.rtc_diff); | |
670 | ||
a4e623fb | 671 | count = os_area_flash_write(db, sizeof(struct os_area_db), pos); |
ef2ac63a | 672 | if (count < sizeof(struct os_area_db)) { |
a4e623fb GU |
673 | pr_debug("%s: os_area_flash_write failed %zd\n", __func__, |
674 | count); | |
675 | error = count < 0 ? count : -EIO; | |
ef2ac63a GL |
676 | } |
677 | ||
a4e623fb | 678 | fail: |
ef2ac63a | 679 | kfree(header); |
a4e623fb | 680 | return error; |
ef2ac63a GL |
681 | } |
682 | ||
418ef209 GL |
683 | /** |
684 | * os_area_queue_work_handler - Asynchronous write handler. | |
685 | * | |
686 | * An asynchronous write for flash memory and the device tree. Do not | |
687 | * call directly, use os_area_queue_work(). | |
688 | */ | |
689 | ||
690 | static void os_area_queue_work_handler(struct work_struct *work) | |
691 | { | |
7db19421 | 692 | struct device_node *node; |
a4e623fb | 693 | int error; |
7db19421 | 694 | |
418ef209 GL |
695 | pr_debug(" -> %s:%d\n", __func__, __LINE__); |
696 | ||
7db19421 | 697 | node = of_find_node_by_path("/"); |
7db19421 GL |
698 | if (node) { |
699 | os_area_set_property(node, &property_rtc_diff); | |
700 | of_node_put(node); | |
701 | } else | |
702 | pr_debug("%s:%d of_find_node_by_path failed\n", | |
703 | __func__, __LINE__); | |
704 | ||
a4e623fb GU |
705 | error = update_flash_db(); |
706 | if (error) | |
707 | pr_warning("%s: Could not update FLASH ROM\n", __func__); | |
708 | ||
418ef209 GL |
709 | pr_debug(" <- %s:%d\n", __func__, __LINE__); |
710 | } | |
711 | ||
712 | static void os_area_queue_work(void) | |
713 | { | |
714 | static DECLARE_WORK(q, os_area_queue_work_handler); | |
715 | ||
716 | wmb(); | |
717 | schedule_work(&q); | |
718 | } | |
719 | ||
01263e88 GL |
720 | /** |
721 | * ps3_os_area_save_params - Copy data from os area mirror to @saved_params. | |
722 | * | |
ef2ac63a | 723 | * For the convenience of the guest the HV makes a copy of the os area in |
01263e88 | 724 | * flash to a high address in the boot memory region and then puts that RAM |
ef2ac63a | 725 | * address and the byte count into the repository for retrieval by the guest. |
01263e88 | 726 | * We copy the data we want into a static variable and allow the memory setup |
95f72d1e | 727 | * by the HV to be claimed by the memblock manager. |
ef2ac63a GL |
728 | * |
729 | * The os area mirror will not be available to a second stage kernel, and | |
730 | * the header verify will fail. In this case, the saved_params values will | |
731 | * be set from flash memory or the passed in device tree in ps3_os_area_init(). | |
01263e88 GL |
732 | */ |
733 | ||
734 | void __init ps3_os_area_save_params(void) | |
00a3e2e9 GL |
735 | { |
736 | int result; | |
737 | u64 lpar_addr; | |
738 | unsigned int size; | |
739 | struct os_area_header *header; | |
740 | struct os_area_params *params; | |
ef2ac63a | 741 | struct os_area_db *db; |
00a3e2e9 | 742 | |
01263e88 GL |
743 | pr_debug(" -> %s:%d\n", __func__, __LINE__); |
744 | ||
00a3e2e9 GL |
745 | result = ps3_repository_read_boot_dat_info(&lpar_addr, &size); |
746 | ||
747 | if (result) { | |
748 | pr_debug("%s:%d ps3_repository_read_boot_dat_info failed\n", | |
749 | __func__, __LINE__); | |
01263e88 | 750 | return; |
00a3e2e9 GL |
751 | } |
752 | ||
753 | header = (struct os_area_header *)__va(lpar_addr); | |
ca94297f GL |
754 | params = (struct os_area_params *)__va(lpar_addr |
755 | + OS_AREA_SEGMENT_SIZE); | |
00a3e2e9 GL |
756 | |
757 | result = verify_header(header); | |
758 | ||
759 | if (result) { | |
7db19421 | 760 | /* Second stage kernels exit here. */ |
00a3e2e9 GL |
761 | pr_debug("%s:%d verify_header failed\n", __func__, __LINE__); |
762 | dump_header(header); | |
01263e88 | 763 | return; |
00a3e2e9 GL |
764 | } |
765 | ||
ef2ac63a GL |
766 | db = (struct os_area_db *)__va(lpar_addr |
767 | + header->db_area_offset * OS_AREA_SEGMENT_SIZE); | |
768 | ||
00a3e2e9 GL |
769 | dump_header(header); |
770 | dump_params(params); | |
ef2ac63a | 771 | dump_db(db); |
00a3e2e9 | 772 | |
ef2ac63a GL |
773 | result = db_verify(db) || db_get_rtc_diff(db, &saved_params.rtc_diff); |
774 | if (result) | |
775 | saved_params.rtc_diff = params->rtc_diff ? params->rtc_diff | |
776 | : SECONDS_FROM_1970_TO_2000; | |
00a3e2e9 | 777 | saved_params.av_multi_out = params->av_multi_out; |
7db19421 | 778 | saved_params.valid = 1; |
00a3e2e9 | 779 | |
01263e88 GL |
780 | memset(header, 0, sizeof(*header)); |
781 | ||
782 | pr_debug(" <- %s:%d\n", __func__, __LINE__); | |
00a3e2e9 GL |
783 | } |
784 | ||
7db19421 GL |
785 | /** |
786 | * ps3_os_area_init - Setup os area device tree properties as needed. | |
787 | */ | |
788 | ||
789 | void __init ps3_os_area_init(void) | |
790 | { | |
791 | struct device_node *node; | |
792 | ||
793 | pr_debug(" -> %s:%d\n", __func__, __LINE__); | |
794 | ||
795 | node = of_find_node_by_path("/"); | |
796 | ||
797 | if (!saved_params.valid && node) { | |
798 | /* Second stage kernels should have a dt entry. */ | |
799 | os_area_get_property(node, &property_rtc_diff); | |
800 | os_area_get_property(node, &property_av_multi_out); | |
801 | } | |
802 | ||
803 | if(!saved_params.rtc_diff) | |
804 | saved_params.rtc_diff = SECONDS_FROM_1970_TO_2000; | |
805 | ||
806 | if (node) { | |
807 | os_area_set_property(node, &property_rtc_diff); | |
808 | os_area_set_property(node, &property_av_multi_out); | |
809 | of_node_put(node); | |
810 | } else | |
811 | pr_debug("%s:%d of_find_node_by_path failed\n", | |
812 | __func__, __LINE__); | |
813 | ||
814 | pr_debug(" <- %s:%d\n", __func__, __LINE__); | |
815 | } | |
816 | ||
00a3e2e9 | 817 | /** |
d7b98e3d | 818 | * ps3_os_area_get_rtc_diff - Returns the rtc diff value. |
00a3e2e9 GL |
819 | */ |
820 | ||
d7b98e3d | 821 | u64 ps3_os_area_get_rtc_diff(void) |
00a3e2e9 | 822 | { |
7db19421 | 823 | return saved_params.rtc_diff; |
00a3e2e9 | 824 | } |
47cb996b | 825 | EXPORT_SYMBOL_GPL(ps3_os_area_get_rtc_diff); |
098e2744 | 826 | |
d7b98e3d GL |
827 | /** |
828 | * ps3_os_area_set_rtc_diff - Set the rtc diff value. | |
829 | * | |
830 | * An asynchronous write is needed to support writing updates from | |
831 | * the timer interrupt context. | |
832 | */ | |
833 | ||
834 | void ps3_os_area_set_rtc_diff(u64 rtc_diff) | |
835 | { | |
836 | if (saved_params.rtc_diff != rtc_diff) { | |
837 | saved_params.rtc_diff = rtc_diff; | |
838 | os_area_queue_work(); | |
839 | } | |
840 | } | |
47cb996b | 841 | EXPORT_SYMBOL_GPL(ps3_os_area_set_rtc_diff); |
d7b98e3d | 842 | |
098e2744 GU |
843 | /** |
844 | * ps3_os_area_get_av_multi_out - Returns the default video mode. | |
845 | */ | |
846 | ||
847 | enum ps3_param_av_multi_out ps3_os_area_get_av_multi_out(void) | |
848 | { | |
849 | return saved_params.av_multi_out; | |
850 | } | |
851 | EXPORT_SYMBOL_GPL(ps3_os_area_get_av_multi_out); |