lib: sbi: Implement system suspend
[platform/kernel/opensbi.git] / lib / sbi / sbi_system.c
1 /*
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2019 Western Digital Corporation or its affiliates.
5  *
6  * Authors:
7  *   Anup Patel <anup.patel@wdc.com>
8  *   Nick Kossifidis <mick@ics.forth.gr>
9  */
10
11 #include <sbi/riscv_asm.h>
12 #include <sbi/sbi_bitops.h>
13 #include <sbi/sbi_domain.h>
14 #include <sbi/sbi_hart.h>
15 #include <sbi/sbi_hsm.h>
16 #include <sbi/sbi_platform.h>
17 #include <sbi/sbi_system.h>
18 #include <sbi/sbi_ipi.h>
19 #include <sbi/sbi_init.h>
20
21 static SBI_LIST_HEAD(reset_devices_list);
22
23 const struct sbi_system_reset_device *sbi_system_reset_get_device(
24                                         u32 reset_type, u32 reset_reason)
25 {
26         struct sbi_system_reset_device *reset_dev = NULL;
27         struct sbi_dlist *pos;
28         /** lowest priority - any non zero is our candidate */
29         int priority = 0;
30
31         /* Check each reset device registered for supported reset type */
32         sbi_list_for_each(pos, &(reset_devices_list)) {
33                 struct sbi_system_reset_device *dev =
34                         to_system_reset_device(pos);
35                 if (dev->system_reset_check) {
36                         int status = dev->system_reset_check(reset_type,
37                                                              reset_reason);
38                         /** reset_type not supported */
39                         if (status == 0)
40                                 continue;
41
42                         if (status > priority) {
43                                 reset_dev = dev;
44                                 priority = status;
45                         }
46                 }
47         }
48
49         return reset_dev;
50 }
51
52 void sbi_system_reset_add_device(struct sbi_system_reset_device *dev)
53 {
54         if (!dev || !dev->system_reset_check)
55                 return;
56
57         sbi_list_add(&(dev->node), &(reset_devices_list));
58 }
59
60 bool sbi_system_reset_supported(u32 reset_type, u32 reset_reason)
61 {
62         return !!sbi_system_reset_get_device(reset_type, reset_reason);
63 }
64
65 void __noreturn sbi_system_reset(u32 reset_type, u32 reset_reason)
66 {
67         ulong hbase = 0, hmask;
68         u32 cur_hartid = current_hartid();
69         struct sbi_domain *dom = sbi_domain_thishart_ptr();
70         struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
71
72         /* Send HALT IPI to every hart other than the current hart */
73         while (!sbi_hsm_hart_interruptible_mask(dom, hbase, &hmask)) {
74                 if (hbase <= cur_hartid)
75                         hmask &= ~(1UL << (cur_hartid - hbase));
76                 if (hmask)
77                         sbi_ipi_send_halt(hmask, hbase);
78                 hbase += BITS_PER_LONG;
79         }
80
81         /* Stop current HART */
82         sbi_hsm_hart_stop(scratch, false);
83
84         /* Platform specific reset if domain allowed system reset */
85         if (dom->system_reset_allowed) {
86                 const struct sbi_system_reset_device *dev =
87                         sbi_system_reset_get_device(reset_type, reset_reason);
88                 if (dev)
89                         dev->system_reset(reset_type, reset_reason);
90         }
91
92         /* If platform specific reset did not work then do sbi_exit() */
93         sbi_exit(scratch);
94 }
95
96 static const struct sbi_system_suspend_device *suspend_dev = NULL;
97
98 const struct sbi_system_suspend_device *sbi_system_suspend_get_device(void)
99 {
100         return suspend_dev;
101 }
102
103 void sbi_system_suspend_set_device(struct sbi_system_suspend_device *dev)
104 {
105         if (!dev || suspend_dev)
106                 return;
107
108         suspend_dev = dev;
109 }
110
111 bool sbi_system_suspend_supported(u32 sleep_type)
112 {
113         return suspend_dev && suspend_dev->system_suspend_check &&
114                suspend_dev->system_suspend_check(sleep_type);
115 }
116
117 int sbi_system_suspend(u32 sleep_type, ulong resume_addr, ulong opaque)
118 {
119         int ret = SBI_ENOTSUPP;
120         const struct sbi_domain *dom = sbi_domain_thishart_ptr();
121         struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
122         void (*jump_warmboot)(void) = (void (*)(void))scratch->warmboot_addr;
123         unsigned int hartid = current_hartid();
124         unsigned long prev_mode;
125         unsigned long i;
126
127         if (!dom || !dom->system_suspend_allowed)
128                 return SBI_EFAIL;
129
130         if (!suspend_dev || !suspend_dev->system_suspend)
131                 return SBI_EFAIL;
132
133         if (!sbi_system_suspend_supported(sleep_type))
134                 return SBI_ENOTSUPP;
135
136         prev_mode = (csr_read(CSR_MSTATUS) & MSTATUS_MPP) >> MSTATUS_MPP_SHIFT;
137         if (prev_mode != PRV_S && prev_mode != PRV_U)
138                 return SBI_EFAIL;
139
140         sbi_hartmask_for_each_hart(i, &dom->assigned_harts) {
141                 if (i == hartid)
142                         continue;
143                 if (__sbi_hsm_hart_get_state(i) != SBI_HSM_STATE_STOPPED)
144                         return SBI_EFAIL;
145         }
146
147         if (!sbi_domain_check_addr(dom, resume_addr, prev_mode,
148                                    SBI_DOMAIN_EXECUTE))
149                 return SBI_EINVALID_ADDR;
150
151         if (!sbi_hsm_hart_change_state(scratch, SBI_HSM_STATE_STARTED,
152                                        SBI_HSM_STATE_SUSPENDED))
153                 return SBI_EFAIL;
154
155         /* Prepare for resume */
156         scratch->next_mode = prev_mode;
157         scratch->next_addr = resume_addr;
158         scratch->next_arg1 = opaque;
159
160         __sbi_hsm_suspend_non_ret_save(scratch);
161
162         /* Suspend */
163         ret = suspend_dev->system_suspend(sleep_type, scratch->warmboot_addr);
164         if (ret != SBI_OK) {
165                 if (!sbi_hsm_hart_change_state(scratch, SBI_HSM_STATE_SUSPENDED,
166                                                SBI_HSM_STATE_STARTED))
167                         sbi_hart_hang();
168                 return ret;
169         }
170
171         /* Resume */
172         jump_warmboot();
173
174         __builtin_unreachable();
175 }