1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * |
4 | * Copyright (C) 2007 Lemote, Inc. & Institute of Computing Technology |
5 | * Author: Fuxin Zhang, zhangfx@lemote.com |
6 | * Copyright (C) 2009 Lemote, Inc. |
7 | * Author: Zhangjin Wu, wuzhangjin@gmail.com |
8 | */ |
9 | #include <linux/cpu.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/init.h> |
12 | #include <linux/kexec.h> |
13 | #include <linux/pm.h> |
14 | #include <linux/slab.h> |
15 | |
16 | #include <asm/bootinfo.h> |
17 | #include <asm/idle.h> |
18 | #include <asm/reboot.h> |
19 | #include <asm/bug.h> |
20 | |
21 | #include <loongson.h> |
22 | #include <boot_param.h> |
23 | |
24 | static void loongson_restart(char *command) |
25 | { |
26 | |
27 | void (*fw_restart)(void) = (void *)loongson_sysconf.restart_addr; |
28 | |
29 | fw_restart(); |
30 | while (1) { |
31 | if (cpu_wait) |
32 | cpu_wait(); |
33 | } |
34 | } |
35 | |
36 | static void loongson_poweroff(void) |
37 | { |
38 | void (*fw_poweroff)(void) = (void *)loongson_sysconf.poweroff_addr; |
39 | |
40 | fw_poweroff(); |
41 | while (1) { |
42 | if (cpu_wait) |
43 | cpu_wait(); |
44 | } |
45 | } |
46 | |
47 | static void loongson_halt(void) |
48 | { |
49 | pr_notice("\n\n** You can safely turn off the power now **\n\n" ); |
50 | while (1) { |
51 | if (cpu_wait) |
52 | cpu_wait(); |
53 | } |
54 | } |
55 | |
56 | #ifdef CONFIG_KEXEC_CORE |
57 | |
58 | /* 0X80000000~0X80200000 is safe */ |
59 | #define MAX_ARGS 64 |
60 | #define KEXEC_CTRL_CODE 0xFFFFFFFF80100000UL |
61 | #define KEXEC_ARGV_ADDR 0xFFFFFFFF80108000UL |
62 | #define KEXEC_ARGV_SIZE COMMAND_LINE_SIZE |
63 | #define KEXEC_ENVP_SIZE 4800 |
64 | |
65 | static int kexec_argc; |
66 | static int kdump_argc; |
67 | static void *kexec_argv; |
68 | static void *kdump_argv; |
69 | static void *kexec_envp; |
70 | |
71 | static int loongson_kexec_prepare(struct kimage *image) |
72 | { |
73 | int i, argc = 0; |
74 | unsigned int *argv; |
75 | char *str, *ptr, *bootloader = "kexec" ; |
76 | |
77 | /* argv at offset 0, argv[] at offset KEXEC_ARGV_SIZE/2 */ |
78 | if (image->type == KEXEC_TYPE_DEFAULT) |
79 | argv = (unsigned int *)kexec_argv; |
80 | else |
81 | argv = (unsigned int *)kdump_argv; |
82 | |
83 | argv[argc++] = (unsigned int)(KEXEC_ARGV_ADDR + KEXEC_ARGV_SIZE/2); |
84 | |
85 | for (i = 0; i < image->nr_segments; i++) { |
86 | if (!strncmp(bootloader, (char *)image->segment[i].buf, |
87 | strlen(bootloader))) { |
88 | /* |
89 | * convert command line string to array |
90 | * of parameters (as bootloader does). |
91 | */ |
92 | int offt; |
93 | str = (char *)argv + KEXEC_ARGV_SIZE/2; |
94 | memcpy(str, image->segment[i].buf, KEXEC_ARGV_SIZE/2); |
95 | ptr = strchr(str, ' '); |
96 | |
97 | while (ptr && (argc < MAX_ARGS)) { |
98 | *ptr = '\0'; |
99 | if (ptr[1] != ' ') { |
100 | offt = (int)(ptr - str + 1); |
101 | argv[argc] = KEXEC_ARGV_ADDR + KEXEC_ARGV_SIZE/2 + offt; |
102 | argc++; |
103 | } |
104 | ptr = strchr(ptr + 1, ' '); |
105 | } |
106 | break; |
107 | } |
108 | } |
109 | |
110 | if (image->type == KEXEC_TYPE_DEFAULT) |
111 | kexec_argc = argc; |
112 | else |
113 | kdump_argc = argc; |
114 | |
115 | /* kexec/kdump need a safe page to save reboot_code_buffer */ |
116 | image->control_code_page = virt_to_page((void *)KEXEC_CTRL_CODE); |
117 | |
118 | return 0; |
119 | } |
120 | |
121 | static void loongson_kexec_shutdown(void) |
122 | { |
123 | #ifdef CONFIG_SMP |
124 | int cpu; |
125 | |
126 | /* All CPUs go to reboot_code_buffer */ |
127 | for_each_possible_cpu(cpu) |
128 | if (!cpu_online(cpu)) |
129 | cpu_device_up(dev: get_cpu_device(cpu)); |
130 | |
131 | secondary_kexec_args[0] = TO_UNCAC(0x3ff01000); |
132 | #endif |
133 | kexec_args[0] = kexec_argc; |
134 | kexec_args[1] = fw_arg1; |
135 | kexec_args[2] = fw_arg2; |
136 | memcpy((void *)fw_arg1, kexec_argv, KEXEC_ARGV_SIZE); |
137 | memcpy((void *)fw_arg2, kexec_envp, KEXEC_ENVP_SIZE); |
138 | } |
139 | |
140 | static void loongson_crash_shutdown(struct pt_regs *regs) |
141 | { |
142 | default_machine_crash_shutdown(regs); |
143 | kexec_args[0] = kdump_argc; |
144 | kexec_args[1] = fw_arg1; |
145 | kexec_args[2] = fw_arg2; |
146 | #ifdef CONFIG_SMP |
147 | secondary_kexec_args[0] = TO_UNCAC(0x3ff01000); |
148 | #endif |
149 | memcpy((void *)fw_arg1, kdump_argv, KEXEC_ARGV_SIZE); |
150 | memcpy((void *)fw_arg2, kexec_envp, KEXEC_ENVP_SIZE); |
151 | } |
152 | |
153 | #endif |
154 | |
155 | static int __init mips_reboot_setup(void) |
156 | { |
157 | _machine_restart = loongson_restart; |
158 | _machine_halt = loongson_halt; |
159 | pm_power_off = loongson_poweroff; |
160 | |
161 | #ifdef CONFIG_KEXEC_CORE |
162 | kexec_argv = kmalloc(KEXEC_ARGV_SIZE, GFP_KERNEL); |
163 | if (WARN_ON(!kexec_argv)) |
164 | return -ENOMEM; |
165 | |
166 | kdump_argv = kmalloc(KEXEC_ARGV_SIZE, GFP_KERNEL); |
167 | if (WARN_ON(!kdump_argv)) |
168 | return -ENOMEM; |
169 | |
170 | kexec_envp = kmalloc(KEXEC_ENVP_SIZE, GFP_KERNEL); |
171 | if (WARN_ON(!kexec_envp)) |
172 | return -ENOMEM; |
173 | |
174 | fw_arg1 = KEXEC_ARGV_ADDR; |
175 | memcpy(kexec_envp, (void *)fw_arg2, KEXEC_ENVP_SIZE); |
176 | |
177 | _machine_kexec_prepare = loongson_kexec_prepare; |
178 | _machine_kexec_shutdown = loongson_kexec_shutdown; |
179 | _machine_crash_shutdown = loongson_crash_shutdown; |
180 | #endif |
181 | |
182 | return 0; |
183 | } |
184 | |
185 | arch_initcall(mips_reboot_setup); |
186 | |