Add PS3 ncurses CUI program
[petitboot] / ui / ncurses / ps3-cui.c
1 /*
2  * Petitboot cui bootloader for the PS3 game console
3  *
4  *  Copyright (C) 2009 Sony Computer Entertainment Inc.
5  *  Copyright 2009 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 /*
22  * TODO
23  * removable media event
24  * resize after video mode change
25  * ncurses mouse support
26  * timeout
27  */
28
29 #if defined(HAVE_CONFIG_H)
30 #include "config.h"
31 #endif
32
33 #define _GNU_SOURCE
34 #include <errno.h>
35 #include <getopt.h>
36 #include <signal.h>
37 #include <stdlib.h>
38 #include <string.h>
39
40 #include "log/log.h"
41 #include "talloc/talloc.h"
42 #include "waiter/waiter.h"
43 #include "ui/common/discover-client.h"
44 #include "ui/common/ps3.h"
45 #include "nc-cui.h"
46
47 static void print_version(void)
48 {
49         printf("pb-cui (" PACKAGE_NAME ") " PACKAGE_VERSION "\n");
50 }
51
52 static void print_usage(void)
53 {
54         print_version();
55         printf(
56 "Usage: pb-cui [-h, --help] [-l, --log log-file] [-V, --version]\n");
57 }
58
59 /**
60  * enum opt_value - Tri-state options variables.
61  */
62
63 enum opt_value {opt_undef = 0, opt_yes, opt_no};
64
65 /**
66  * struct opts - Values from command line options.
67  */
68
69 struct opts {
70         enum opt_value show_help;
71         const char *log_file;
72         enum opt_value show_version;
73 };
74
75 /**
76  * opts_parse - Parse the command line options.
77  */
78
79 static int opts_parse(struct opts *opts, int argc, char *argv[])
80 {
81         static const struct option long_options[] = {
82                 {"help",    no_argument,       NULL, 'h'},
83                 {"log",     required_argument, NULL, 'l'},
84                 {"version", no_argument,       NULL, 'V'},
85                 { NULL,     0,                 NULL, 0},
86         };
87         static const char short_options[] = "hl:V";
88         static const struct opts default_values = {
89                 .log_file = "pb-cui.log",
90         };
91
92         *opts = default_values;
93
94         while (1) {
95                 int c = getopt_long(argc, argv, short_options, long_options,
96                         NULL);
97
98                 if (c == EOF)
99                         break;
100
101                 switch (c) {
102                 case 'h':
103                         opts->show_help = opt_yes;
104                         break;
105                 case 'l':
106                         opts->log_file = optarg;
107                         break;
108                 case 'V':
109                         opts->show_version = opt_yes;
110                         break;
111                 default:
112                         opts->show_help = opt_yes;
113                         return -1;
114                 }
115         }
116
117         return 0;
118 }
119
120 /**
121  * struct ps3_cui - Main cui program instance.
122  * @mm: Main menu.
123  * @svm: Set video mode menu.
124  */
125
126 struct ps3_cui {
127         struct pmenu *mm;
128         struct pmenu *svm;
129         struct cui *cui;
130         struct ps3_flash_values values;
131         int dirty_values;
132 };
133
134 static struct ps3_cui *ps3_from_cui(struct cui *cui)
135 {
136         struct ps3_cui *ps3;
137
138         assert(cui->c_sig == pb_cui_sig);
139         ps3 = cui->platform_info;
140         assert(ps3->cui->c_sig == pb_cui_sig);
141         return ps3;
142 }
143
144 static struct ps3_cui *ps3_from_item(struct pmenu_item *item)
145 {
146         return ps3_from_cui(cui_from_item(item));
147 }
148
149 /**
150  * ps3_set_mode - Set video mode helper.
151  *
152  * Runs ps3_set_video_mode().
153  */
154
155 static void ps3_set_mode(struct ps3_cui *ps3, unsigned int mode)
156 {
157         int result;
158
159         if (ps3->values.video_mode == (uint16_t)mode)
160                 return;
161
162         ps3->values.video_mode = (uint16_t)mode;
163         ps3->dirty_values = 1;
164
165         result = ps3_set_video_mode(mode);
166
167         if (result)
168                 nc_scr_status_printf(ps3->cui->current,
169                         "Failed: set_video_mode(%u)", mode);
170 }
171
172 /**
173  * ps3_svm_cb - The set video mode callback.
174  */
175
176 static int ps3_svm_cb(struct pmenu_item *item)
177 {
178         ps3_set_mode(ps3_from_item(item), (unsigned int)item->data);
179         return 0;
180 }
181
182 /**
183  * ps3_kexec_cb - The kexec callback.
184  *
185  * Writes config data to PS3 flash then calls pb_run_kexec().
186  */
187
188 static int ps3_kexec_cb(struct cui *cui, struct cui_opt_data *cod)
189 {
190         struct ps3_cui *ps3 = ps3_from_cui(cui);
191
192         pb_log("%s: %s:%s\n", __func__, cod->dev->name, cod->opt->name);
193
194         assert(ps3->cui->current == &ps3->cui->main->scr);
195
196         if (cui->default_item != cod->opt_hash || ps3->dirty_values) {
197                 ps3->values.default_item = cod->opt_hash;
198                 ps3_flash_set_values(&ps3->values);
199         }
200
201         return pb_run_kexec(cod->kd);
202 }
203
204 /**
205  * ps3_mm_to_svm_cb - Callback to switch to the set video mode menu.
206  */
207
208 static int ps3_mm_to_svm_cb(struct pmenu_item *item)
209 {
210         struct ps3_cui *ps3 = ps3_from_item(item);
211         struct nc_scr *old;
212
213         old = cui_set_current(ps3->cui, &ps3->svm->scr);
214         assert(old == &ps3->mm->scr);
215
216         return 0;
217 }
218
219 /**
220  * ps3_svm_to_mm_cb - Callback to switch back to the main menu.
221  */
222
223 static int ps3_svm_to_mm_cb(struct pmenu_item *item)
224 {
225         struct ps3_cui *ps3 = ps3_from_item(item);
226         struct nc_scr *old;
227
228         old = cui_set_current(ps3->cui, &ps3->mm->scr);
229         assert(old == &ps3->svm->scr);
230
231         return 0;
232 }
233
234 /**
235  * ps3_svm_to_mm_helper - The svm ESC callback.
236  */
237
238 static void ps3_svm_to_mm_helper(struct pmenu *menu)
239 {
240         ps3_svm_to_mm_cb(pmenu_find_selected(menu));
241 }
242
243 /**
244  * ps3_hot_key - PS3 specific hot keys.
245  *
246  * '@' = Set video mode to auto (mode 0)
247  * '$' = Set video mode to safe (480i)
248  * '+' = Cycles through a set of common video modes.
249  * '-' = Cycles through a set of common video modes in reverse.
250  */
251
252 static int ps3_hot_key(struct pmenu __attribute__((unused)) *menu,
253         struct pmenu_item *item, int c)
254 {
255         static const unsigned int modes[] = {0, 1, 6, 3, 11, 12};
256         static const unsigned int *const end = modes
257                 + sizeof(modes) / sizeof(modes[0]) - 1;
258         static const unsigned int *p = modes;
259
260         switch (c) {
261         default:
262                 /* DBGS("%d (%o)\n", c, c); */
263                 break;
264         case '@':
265                 p = modes + 0;
266                 ps3_set_mode(ps3_from_item(item), *p);
267                 break;
268         case '$':
269                 p = modes + 1;
270                 ps3_set_mode(ps3_from_item(item), *p);
271                 break;
272         case '+':
273                 p = (p < end) ? p + 1 : modes;
274                 ps3_set_mode(ps3_from_item(item), *p);
275                 break;
276         case '-':
277                 p = (p > modes) ? p - 1 : end;
278                 ps3_set_mode(ps3_from_item(item), *p);
279                 break;
280         }
281
282         return c;
283 }
284
285 /**
286  * ps3_mm_init - Setup the main menu instance.
287  */
288
289 static struct pmenu *ps3_mm_init(struct ps3_cui *ps3_cui)
290 {
291         int result;
292         struct pmenu *m;
293         struct pmenu_item *i;
294         static const char *const bgo[] =
295                 {"/usr/sbin/ps3-boot-game-os-NOT", NULL};
296
297         m = pmenu_init(ps3_cui->cui, 3, cui_on_exit);
298
299         if (!m) {
300                 pb_log("%s: failed\n", __func__);
301                 return NULL;
302         }
303
304         m->hot_key = ps3_hot_key;
305         m->scr.frame.title = talloc_strdup(m, "Petitboot PS3");
306         m->scr.frame.help = talloc_strdup(m,
307                 "ESC=exit, Enter=accept, E,e=edit");
308         m->scr.frame.status = talloc_strdup(m, "Welcome to Petitboot");
309
310         i = pmenu_item_init(m, 0, "Boot GameOS",
311                 "Reboot the PS3 into the GameOS");
312         i->on_execute = cui_run_cmd;
313         i->data = (void *)bgo;
314
315         i = pmenu_item_init(m, 1, "Set Video Mode",
316                 "Display a video mode selection menu");
317         i->on_execute = ps3_mm_to_svm_cb;
318
319         i = pmenu_item_init(m, 2, "Exit to Shell",
320                 "Exit petitboot and return to a shell prompt");
321         i->on_execute = pmenu_exit_cb;
322
323         result = pmenu_setup(m);
324
325         if (result) {
326                 pb_log("%s:%d: pmenu_setup failed: %s\n", __func__, __LINE__,
327                         strerror(errno));
328                 goto fail_setup;
329         }
330
331         menu_opts_off(m->ncm, O_SHOWDESC);
332         set_menu_mark(m->ncm, " *");
333         set_current_item(m->ncm, i->nci);
334
335         return m;
336
337 fail_setup:
338         talloc_free(m);
339         return NULL;
340 }
341
342 /**
343  * ps3_svm_init - Setup the set video mode menu instance.
344  */
345
346 static struct pmenu *ps3_svm_init(struct ps3_cui *ps3_cui)
347 {
348         int result;
349         struct pmenu *m;
350         struct pmenu_item *i;
351
352         m = pmenu_init(ps3_cui->cui, 12, ps3_svm_to_mm_helper);
353
354         if (!m) {
355                 pb_log("%s: failed\n", __func__);
356                 return NULL;
357         }
358
359         m->hot_key = ps3_hot_key;
360         m->scr.frame.title = talloc_strdup(m, "Select PS3 Video Mode");
361         m->scr.frame.help = talloc_strdup(m, "ESC=exit, Enter=accept");
362
363         i = pmenu_item_init(m, 0, "auto detect",
364                 "Auto detect the best HDMI video mode");
365         i->on_execute = ps3_svm_cb;
366         i->data = (void *)0;
367
368         i = pmenu_item_init(m, 1, "480i    (576 x 384)", NULL);
369         i->on_execute = ps3_svm_cb;
370         i->data = (void *)1;
371
372         i = pmenu_item_init(m, 2, "480p    (576 x 384)", NULL);
373         i->on_execute = ps3_svm_cb;
374         i->data = (void *)2;
375
376         i = pmenu_item_init(m, 3, "576i    (576 x 460)", NULL);
377         i->on_execute = ps3_svm_cb;
378         i->data = (void *)6;
379
380         i = pmenu_item_init(m, 4, "576p    (576 x 460)", NULL);
381         i->on_execute = ps3_svm_cb;
382         i->data = (void *)7;
383
384         i = pmenu_item_init(m, 5, "720p   (1124 x 644)", NULL);
385         i->on_execute = ps3_svm_cb;
386         i->data = (void *)3;
387
388         i = pmenu_item_init(m, 6, "1080i  (1688 x 964)", NULL);
389         i->on_execute = ps3_svm_cb;
390         i->data = (void *)4;
391
392         i = pmenu_item_init(m, 7, "1080p  (1688 x 964)", NULL);
393         i->on_execute = ps3_svm_cb;
394         i->data = (void *)5;
395
396         i = pmenu_item_init(m, 8, "wxga   (1280 x 768)", NULL);
397         i->on_execute = ps3_svm_cb;
398         i->data = (void *)11;
399
400         i = pmenu_item_init(m, 9, "sxga   (1280 x 1024)", NULL);
401         i->on_execute = ps3_svm_cb;
402         i->data = (void *)12;
403
404         i = pmenu_item_init(m, 10, "wuxga  (1920 x 1200)", NULL);
405         i->on_execute = ps3_svm_cb;
406         i->data = (void *)13;
407
408         i = pmenu_item_init(m, 11, "Return",
409                 "Return to the main menu");
410         i->on_execute = ps3_svm_to_mm_cb;
411
412         result = pmenu_setup(m);
413
414         if (result) {
415                 pb_log("%s:%d: pmenu_setup failed: %s\n", __func__, __LINE__,
416                         strerror(errno));
417                 goto fail_setup;
418         }
419
420         menu_opts_off(m->ncm, O_SHOWDESC);
421         set_menu_mark(m->ncm, " *");
422
423         return m;
424
425 fail_setup:
426         talloc_free(m);
427         return NULL;
428 }
429
430 static struct ps3_cui ps3;
431
432 static void sig_handler(int signum)
433 {
434         DBGS("%d\n", signum);
435
436         switch (signum) {
437         case SIGWINCH:
438                 if (ps3.cui)
439                         cui_resize(ps3.cui);
440                 break;
441         default:
442                 assert(0 && "unknown sig");
443                 /* fall through */
444         case SIGINT:
445         case SIGHUP:
446         case SIGTERM:
447                 if (ps3.cui)
448                         cui_abort(ps3.cui);
449                 break;
450         }
451 }
452
453 /**
454  * main - cui bootloader main routine.
455  */
456
457 int main(int argc, char *argv[])
458 {
459         static struct sigaction sa;
460         static struct opts opts;
461         int result;
462         int cui_result;
463         unsigned int mode;
464         FILE *log;
465
466         result = opts_parse(&opts, argc, argv);
467
468         if (result) {
469                 print_usage();
470                 return EXIT_FAILURE;
471         }
472
473         if (opts.show_help == opt_yes) {
474                 print_usage();
475                 return EXIT_SUCCESS;
476         }
477
478         if (opts.show_version == opt_yes) {
479                 print_version();
480                 return EXIT_SUCCESS;
481         }
482
483         log = fopen(opts.log_file, "a");
484         assert(log);
485         pb_log_set_stream(log);
486
487 #if defined(DEBUG)
488         pb_log_always_flush(1);
489 #endif
490
491         pb_log("--- pb-cui ---\n");
492
493         sa.sa_handler = sig_handler;
494         result = sigaction(SIGINT, &sa, NULL);
495         result += sigaction(SIGHUP, &sa, NULL);
496         result += sigaction(SIGTERM, &sa, NULL);
497         result += sigaction(SIGWINCH, &sa, NULL);
498
499         if (result) {
500                 pb_log("%s sigaction failed.\n", __func__);
501                 return EXIT_FAILURE;
502         }
503
504         ps3.dirty_values = ps3_flash_get_values(&ps3.values);
505
506         result = ps3_get_video_mode(&mode);
507
508         /* Current becomes default if ps3_flash_get_values() failed. */
509
510         if (ps3.dirty_values && !result)
511                 ps3.values.video_mode = mode;
512
513         /* Set mode if not at default. */
514
515         if (!result && (ps3.values.video_mode != (uint16_t)mode))
516                 ps3_set_video_mode(ps3.values.video_mode);
517
518         ps3.cui = cui_init(&ps3, ps3_kexec_cb);
519
520         if (!ps3.cui)
521                 return EXIT_FAILURE;
522
523         ps3.mm = ps3_mm_init(&ps3);
524         ps3.svm = ps3_svm_init(&ps3);
525
526         cui_result = cui_run(ps3.cui, ps3.mm, ps3.values.default_item);
527
528         pmenu_delete(ps3.mm);
529         pmenu_delete(ps3.svm);
530
531         if (ps3.dirty_values)
532                 ps3_flash_set_values(&ps3.values);
533
534         talloc_free(ps3.cui);
535
536         pb_log("--- end ---\n");
537
538         return cui_result ? EXIT_FAILURE : EXIT_SUCCESS;
539 }