]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blob - arch/powerpc/platforms/ps3/os-area.c
e50a276fcf6712c8eeb5a652716732de058a3dfb
[linux-2.6-omap-h63xx.git] / arch / powerpc / platforms / ps3 / os-area.c
1 /*
2  *  PS3 flash memory os area.
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>
23 #include <linux/workqueue.h>
24
25 #include <asm/lmb.h>
26
27 #include "platform.h"
28
29 enum {
30         OS_AREA_SEGMENT_SIZE = 0X200,
31 };
32
33 enum os_area_ldr_format {
34         HEADER_LDR_FORMAT_RAW = 0,
35         HEADER_LDR_FORMAT_GZIP = 1,
36 };
37
38 /**
39  * struct os_area_header - os area header segment.
40  * @magic_num: Always 'cell_ext_os_area'.
41  * @hdr_version: Header format version number.
42  * @os_area_offset: Starting segment number of os image area.
43  * @ldr_area_offset: Starting segment number of bootloader image area.
44  * @ldr_format: HEADER_LDR_FORMAT flag.
45  * @ldr_size: Size of bootloader image in bytes.
46  *
47  * Note that the docs refer to area offsets.  These are offsets in units of
48  * segments from the start of the os area (top of the header).  These are
49  * better thought of as segment numbers.  The os area of the os area is
50  * reserved for the os image.
51  */
52
53 struct os_area_header {
54         u8 magic_num[16];
55         u32 hdr_version;
56         u32 os_area_offset;
57         u32 ldr_area_offset;
58         u32 _reserved_1;
59         u32 ldr_format;
60         u32 ldr_size;
61         u32 _reserved_2[6];
62 };
63
64 enum os_area_boot_flag {
65         PARAM_BOOT_FLAG_GAME_OS = 0,
66         PARAM_BOOT_FLAG_OTHER_OS = 1,
67 };
68
69 enum os_area_ctrl_button {
70         PARAM_CTRL_BUTTON_O_IS_YES = 0,
71         PARAM_CTRL_BUTTON_X_IS_YES = 1,
72 };
73
74 /**
75  * struct os_area_params - os area params segment.
76  * @boot_flag: User preference of operating system, PARAM_BOOT_FLAG flag.
77  * @num_params: Number of params in this (params) segment.
78  * @rtc_diff: Difference in seconds between 1970 and the ps3 rtc value.
79  * @av_multi_out: User preference of AV output, PARAM_AV_MULTI_OUT flag.
80  * @ctrl_button: User preference of controller button config, PARAM_CTRL_BUTTON
81  *      flag.
82  * @static_ip_addr: User preference of static IP address.
83  * @network_mask: User preference of static network mask.
84  * @default_gateway: User preference of static default gateway.
85  * @dns_primary: User preference of static primary dns server.
86  * @dns_secondary: User preference of static secondary dns server.
87  *
88  * The ps3 rtc maintains a read-only value that approximates seconds since
89  * 2000-01-01 00:00:00 UTC.
90  *
91  * User preference of zero for static_ip_addr means use dhcp.
92  */
93
94 struct os_area_params {
95         u32 boot_flag;
96         u32 _reserved_1[3];
97         u32 num_params;
98         u32 _reserved_2[3];
99         /* param 0 */
100         s64 rtc_diff;
101         u8 av_multi_out;
102         u8 ctrl_button;
103         u8 _reserved_3[6];
104         /* param 1 */
105         u8 static_ip_addr[4];
106         u8 network_mask[4];
107         u8 default_gateway[4];
108         u8 _reserved_4[4];
109         /* param 2 */
110         u8 dns_primary[4];
111         u8 dns_secondary[4];
112         u8 _reserved_5[8];
113 };
114
115 #define SECONDS_FROM_1970_TO_2000 946684800LL
116
117 /**
118  * struct saved_params - Static working copies of data from the PS3 'os area'.
119  */
120
121 struct saved_params {
122         unsigned int valid;
123         s64 rtc_diff;
124         unsigned int av_multi_out;
125 } static saved_params;
126
127 static struct property property_rtc_diff = {
128         .name = "linux,rtc_diff",
129         .length = sizeof(saved_params.rtc_diff),
130         .value = &saved_params.rtc_diff,
131 };
132
133 static struct property property_av_multi_out = {
134         .name = "linux,av_multi_out",
135         .length = sizeof(saved_params.av_multi_out),
136         .value = &saved_params.av_multi_out,
137 };
138
139 /**
140  * os_area_set_property - Add or overwrite a saved_params value to the device tree.
141  *
142  * Overwrites an existing property.
143  */
144
145 static void os_area_set_property(struct device_node *node,
146         struct property *prop)
147 {
148         int result;
149         struct property *tmp = of_find_property(node, prop->name, NULL);
150
151         if (tmp) {
152                 pr_debug("%s:%d found %s\n", __func__, __LINE__, prop->name);
153                 prom_remove_property(node, tmp);
154         }
155
156         result = prom_add_property(node, prop);
157
158         if (result)
159                 pr_debug("%s:%d prom_set_property failed\n", __func__,
160                         __LINE__);
161 }
162
163 /**
164  * os_area_get_property - Get a saved_params value from the device tree.
165  *
166  */
167
168 static void __init os_area_get_property(struct device_node *node,
169         struct property *prop)
170 {
171         const struct property *tmp = of_find_property(node, prop->name, NULL);
172
173         if (tmp) {
174                 BUG_ON(prop->length != tmp->length);
175                 memcpy(prop->value, tmp->value, prop->length);
176         } else
177                 pr_debug("%s:%d not found %s\n", __func__, __LINE__,
178                         prop->name);
179 }
180
181 #define dump_header(_a) _dump_header(_a, __func__, __LINE__)
182 static void _dump_header(const struct os_area_header *h, const char *func,
183         int line)
184 {
185         pr_debug("%s:%d: h.magic_num:         '%s'\n", func, line,
186                 h->magic_num);
187         pr_debug("%s:%d: h.hdr_version:       %u\n", func, line,
188                 h->hdr_version);
189         pr_debug("%s:%d: h.os_area_offset:   %u\n", func, line,
190                 h->os_area_offset);
191         pr_debug("%s:%d: h.ldr_area_offset: %u\n", func, line,
192                 h->ldr_area_offset);
193         pr_debug("%s:%d: h.ldr_format:        %u\n", func, line,
194                 h->ldr_format);
195         pr_debug("%s:%d: h.ldr_size:          %xh\n", func, line,
196                 h->ldr_size);
197 }
198
199 #define dump_params(_a) _dump_params(_a, __func__, __LINE__)
200 static void _dump_params(const struct os_area_params *p, const char *func,
201         int line)
202 {
203         pr_debug("%s:%d: p.boot_flag:       %u\n", func, line, p->boot_flag);
204         pr_debug("%s:%d: p.num_params:      %u\n", func, line, p->num_params);
205         pr_debug("%s:%d: p.rtc_diff         %ld\n", func, line, p->rtc_diff);
206         pr_debug("%s:%d: p.av_multi_out     %u\n", func, line, p->av_multi_out);
207         pr_debug("%s:%d: p.ctrl_button:     %u\n", func, line, p->ctrl_button);
208         pr_debug("%s:%d: p.static_ip_addr:  %u.%u.%u.%u\n", func, line,
209                 p->static_ip_addr[0], p->static_ip_addr[1],
210                 p->static_ip_addr[2], p->static_ip_addr[3]);
211         pr_debug("%s:%d: p.network_mask:    %u.%u.%u.%u\n", func, line,
212                 p->network_mask[0], p->network_mask[1],
213                 p->network_mask[2], p->network_mask[3]);
214         pr_debug("%s:%d: p.default_gateway: %u.%u.%u.%u\n", func, line,
215                 p->default_gateway[0], p->default_gateway[1],
216                 p->default_gateway[2], p->default_gateway[3]);
217         pr_debug("%s:%d: p.dns_primary:     %u.%u.%u.%u\n", func, line,
218                 p->dns_primary[0], p->dns_primary[1],
219                 p->dns_primary[2], p->dns_primary[3]);
220         pr_debug("%s:%d: p.dns_secondary:   %u.%u.%u.%u\n", func, line,
221                 p->dns_secondary[0], p->dns_secondary[1],
222                 p->dns_secondary[2], p->dns_secondary[3]);
223 }
224
225 static int __init verify_header(const struct os_area_header *header)
226 {
227         if (memcmp(header->magic_num, "cell_ext_os_area", 16)) {
228                 pr_debug("%s:%d magic_num failed\n", __func__, __LINE__);
229                 return -1;
230         }
231
232         if (header->hdr_version < 1) {
233                 pr_debug("%s:%d hdr_version failed\n", __func__, __LINE__);
234                 return -1;
235         }
236
237         if (header->os_area_offset > header->ldr_area_offset) {
238                 pr_debug("%s:%d offsets failed\n", __func__, __LINE__);
239                 return -1;
240         }
241
242         return 0;
243 }
244
245 /**
246  * os_area_queue_work_handler - Asynchronous write handler.
247  *
248  * An asynchronous write for flash memory and the device tree.  Do not
249  * call directly, use os_area_queue_work().
250  */
251
252 static void os_area_queue_work_handler(struct work_struct *work)
253 {
254         struct device_node *node;
255
256         pr_debug(" -> %s:%d\n", __func__, __LINE__);
257
258         node = of_find_node_by_path("/");
259
260         if (node) {
261                 os_area_set_property(node, &property_rtc_diff);
262                 of_node_put(node);
263         } else
264                 pr_debug("%s:%d of_find_node_by_path failed\n",
265                         __func__, __LINE__);
266
267         pr_debug(" <- %s:%d\n", __func__, __LINE__);
268 }
269
270 static void os_area_queue_work(void)
271 {
272         static DECLARE_WORK(q, os_area_queue_work_handler);
273
274         wmb();
275         schedule_work(&q);
276 }
277
278 /**
279  * ps3_os_area_save_params - Copy data from os area mirror to @saved_params.
280  *
281  * For the convenience of the guest, the HV makes a copy of the os area in
282  * flash to a high address in the boot memory region and then puts that RAM
283  * address and the byte count into the repository for retreval by the guest.
284  * We copy the data we want into a static variable and allow the memory setup
285  * by the HV to be claimed by the lmb manager.
286  */
287
288 void __init ps3_os_area_save_params(void)
289 {
290         int result;
291         u64 lpar_addr;
292         unsigned int size;
293         struct os_area_header *header;
294         struct os_area_params *params;
295
296         pr_debug(" -> %s:%d\n", __func__, __LINE__);
297
298         result = ps3_repository_read_boot_dat_info(&lpar_addr, &size);
299
300         if (result) {
301                 pr_debug("%s:%d ps3_repository_read_boot_dat_info failed\n",
302                         __func__, __LINE__);
303                 return;
304         }
305
306         header = (struct os_area_header *)__va(lpar_addr);
307         params = (struct os_area_params *)__va(lpar_addr
308                 + OS_AREA_SEGMENT_SIZE);
309
310         result = verify_header(header);
311
312         if (result) {
313                 /* Second stage kernels exit here. */
314
315                 pr_debug("%s:%d verify_header failed\n", __func__, __LINE__);
316                 dump_header(header);
317                 return;
318         }
319
320         dump_header(header);
321         dump_params(params);
322
323         saved_params.rtc_diff = params->rtc_diff;
324         saved_params.av_multi_out = params->av_multi_out;
325         saved_params.valid = 1;
326
327         memset(header, 0, sizeof(*header));
328
329         pr_debug(" <- %s:%d\n", __func__, __LINE__);
330 }
331
332 /**
333  * ps3_os_area_init - Setup os area device tree properties as needed.
334  */
335
336 void __init ps3_os_area_init(void)
337 {
338         struct device_node *node;
339
340         pr_debug(" -> %s:%d\n", __func__, __LINE__);
341
342         node = of_find_node_by_path("/");
343
344         if (!saved_params.valid && node) {
345                 /* Second stage kernels should have a dt entry. */
346                 os_area_get_property(node, &property_rtc_diff);
347                 os_area_get_property(node, &property_av_multi_out);
348         }
349
350         if(!saved_params.rtc_diff)
351                 saved_params.rtc_diff = SECONDS_FROM_1970_TO_2000;
352
353         if (node) {
354                 os_area_set_property(node, &property_rtc_diff);
355                 os_area_set_property(node, &property_av_multi_out);
356                 of_node_put(node);
357         } else
358                 pr_debug("%s:%d of_find_node_by_path failed\n",
359                         __func__, __LINE__);
360
361         pr_debug(" <- %s:%d\n", __func__, __LINE__);
362 }
363
364 /**
365  * ps3_os_area_get_rtc_diff - Returns the rtc diff value.
366  */
367
368 u64 ps3_os_area_get_rtc_diff(void)
369 {
370         return saved_params.rtc_diff;
371 }
372
373 /**
374  * ps3_os_area_set_rtc_diff - Set the rtc diff value.
375  *
376  * An asynchronous write is needed to support writing updates from
377  * the timer interrupt context.
378  */
379
380 void ps3_os_area_set_rtc_diff(u64 rtc_diff)
381 {
382         if (saved_params.rtc_diff != rtc_diff) {
383                 saved_params.rtc_diff = rtc_diff;
384                 os_area_queue_work();
385         }
386 }
387
388 /**
389  * ps3_os_area_get_av_multi_out - Returns the default video mode.
390  */
391
392 enum ps3_param_av_multi_out ps3_os_area_get_av_multi_out(void)
393 {
394     return saved_params.av_multi_out;
395 }
396 EXPORT_SYMBOL_GPL(ps3_os_area_get_av_multi_out);