1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * HSM extension and cpu_ops implementation. |
4 | * |
5 | * Copyright (c) 2020 Western Digital Corporation or its affiliates. |
6 | */ |
7 | |
8 | #include <linux/init.h> |
9 | #include <linux/mm.h> |
10 | #include <linux/sched/task_stack.h> |
11 | #include <asm/cpu_ops.h> |
12 | #include <asm/cpu_ops_sbi.h> |
13 | #include <asm/sbi.h> |
14 | #include <asm/smp.h> |
15 | |
16 | extern char secondary_start_sbi[]; |
17 | const struct cpu_operations cpu_ops_sbi; |
18 | |
19 | /* |
20 | * Ordered booting via HSM brings one cpu at a time. However, cpu hotplug can |
21 | * be invoked from multiple threads in parallel. Define a per cpu data |
22 | * to handle that. |
23 | */ |
24 | static DEFINE_PER_CPU(struct sbi_hart_boot_data, boot_data); |
25 | |
26 | static int sbi_hsm_hart_start(unsigned long hartid, unsigned long saddr, |
27 | unsigned long priv) |
28 | { |
29 | struct sbiret ret; |
30 | |
31 | ret = sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_START, |
32 | hartid, saddr, priv, 0, 0, 0); |
33 | if (ret.error) |
34 | return sbi_err_map_linux_errno(ret.error); |
35 | else |
36 | return 0; |
37 | } |
38 | |
39 | #ifdef CONFIG_HOTPLUG_CPU |
40 | static int sbi_hsm_hart_stop(void) |
41 | { |
42 | struct sbiret ret; |
43 | |
44 | ret = sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_STOP, 0, 0, 0, 0, 0, 0); |
45 | |
46 | if (ret.error) |
47 | return sbi_err_map_linux_errno(ret.error); |
48 | else |
49 | return 0; |
50 | } |
51 | |
52 | static int sbi_hsm_hart_get_status(unsigned long hartid) |
53 | { |
54 | struct sbiret ret; |
55 | |
56 | ret = sbi_ecall(SBI_EXT_HSM, SBI_EXT_HSM_HART_STATUS, |
57 | hartid, 0, 0, 0, 0, 0); |
58 | if (ret.error) |
59 | return sbi_err_map_linux_errno(ret.error); |
60 | else |
61 | return ret.value; |
62 | } |
63 | #endif |
64 | |
65 | static int sbi_cpu_start(unsigned int cpuid, struct task_struct *tidle) |
66 | { |
67 | unsigned long boot_addr = __pa_symbol(secondary_start_sbi); |
68 | unsigned long hartid = cpuid_to_hartid_map(cpuid); |
69 | unsigned long hsm_data; |
70 | struct sbi_hart_boot_data *bdata = &per_cpu(boot_data, cpuid); |
71 | |
72 | /* Make sure tidle is updated */ |
73 | smp_mb(); |
74 | bdata->task_ptr = tidle; |
75 | bdata->stack_ptr = task_stack_page(task: tidle) + THREAD_SIZE; |
76 | /* Make sure boot data is updated */ |
77 | smp_mb(); |
78 | hsm_data = __pa(bdata); |
79 | return sbi_hsm_hart_start(hartid, saddr: boot_addr, priv: hsm_data); |
80 | } |
81 | |
82 | #ifdef CONFIG_HOTPLUG_CPU |
83 | static void sbi_cpu_stop(void) |
84 | { |
85 | int ret; |
86 | |
87 | ret = sbi_hsm_hart_stop(); |
88 | pr_crit("Unable to stop the cpu %u (%d)\n" , smp_processor_id(), ret); |
89 | } |
90 | |
91 | static int sbi_cpu_is_stopped(unsigned int cpuid) |
92 | { |
93 | int rc; |
94 | unsigned long hartid = cpuid_to_hartid_map(cpuid); |
95 | |
96 | rc = sbi_hsm_hart_get_status(hartid); |
97 | |
98 | if (rc == SBI_HSM_STATE_STOPPED) |
99 | return 0; |
100 | return rc; |
101 | } |
102 | #endif |
103 | |
104 | const struct cpu_operations cpu_ops_sbi = { |
105 | .cpu_start = sbi_cpu_start, |
106 | #ifdef CONFIG_HOTPLUG_CPU |
107 | .cpu_stop = sbi_cpu_stop, |
108 | .cpu_is_stopped = sbi_cpu_is_stopped, |
109 | #endif |
110 | }; |
111 | |