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