summaryrefslogtreecommitdiff
path: root/lib/sbi/sbi_system.c
blob: 7fa5ea82b7a6c14b3c7f12b39813d317cfc25f67 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/*
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2019 Western Digital Corporation or its affiliates.
 *
 * Authors:
 *   Anup Patel <anup.patel@wdc.com>
 *   Nick Kossifidis <mick@ics.forth.gr>
 */

#include <sbi/riscv_asm.h>
#include <sbi/sbi_bitops.h>
#include <sbi/sbi_domain.h>
#include <sbi/sbi_hart.h>
#include <sbi/sbi_hsm.h>
#include <sbi/sbi_platform.h>
#include <sbi/sbi_system.h>
#include <sbi/sbi_ipi.h>
#include <sbi/sbi_init.h>

static SBI_LIST_HEAD(reset_devices_list);

const struct sbi_system_reset_device *sbi_system_reset_get_device(
					u32 reset_type, u32 reset_reason)
{
	struct sbi_system_reset_device *reset_dev = NULL;
	struct sbi_dlist *pos;
	/** lowest priority - any non zero is our candidate */
	int priority = 0;

	/* Check each reset device registered for supported reset type */
	sbi_list_for_each(pos, &(reset_devices_list)) {
		struct sbi_system_reset_device *dev =
			to_system_reset_device(pos);
		if (dev->system_reset_check) {
			int status = dev->system_reset_check(reset_type,
							     reset_reason);
			/** reset_type not supported */
			if (status == 0)
				continue;

			if (status > priority) {
				reset_dev = dev;
				priority = status;
			}
		}
	}

	return reset_dev;
}

void sbi_system_reset_add_device(struct sbi_system_reset_device *dev)
{
	if (!dev || !dev->system_reset_check)
		return;

	sbi_list_add(&(dev->node), &(reset_devices_list));
}

bool sbi_system_reset_supported(u32 reset_type, u32 reset_reason)
{
	return !!sbi_system_reset_get_device(reset_type, reset_reason);
}

void __noreturn sbi_system_reset(u32 reset_type, u32 reset_reason)
{
	ulong hbase = 0, hmask;
	u32 cur_hartid = current_hartid();
	struct sbi_domain *dom = sbi_domain_thishart_ptr();
	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();

	/* Send HALT IPI to every hart other than the current hart */
	while (!sbi_hsm_hart_interruptible_mask(dom, hbase, &hmask)) {
		if (hbase <= cur_hartid)
			hmask &= ~(1UL << (cur_hartid - hbase));
		if (hmask)
			sbi_ipi_send_halt(hmask, hbase);
		hbase += BITS_PER_LONG;
	}

	/* Stop current HART */
	sbi_hsm_hart_stop(scratch, false);

	/* Platform specific reset if domain allowed system reset */
	if (dom->system_reset_allowed) {
		const struct sbi_system_reset_device *dev =
			sbi_system_reset_get_device(reset_type, reset_reason);
		if (dev)
			dev->system_reset(reset_type, reset_reason);
	}

	/* If platform specific reset did not work then do sbi_exit() */
	sbi_exit(scratch);
}

static const struct sbi_system_suspend_device *suspend_dev = NULL;

const struct sbi_system_suspend_device *sbi_system_suspend_get_device(void)
{
	return suspend_dev;
}

void sbi_system_suspend_set_device(struct sbi_system_suspend_device *dev)
{
	if (!dev || suspend_dev)
		return;

	suspend_dev = dev;
}

bool sbi_system_suspend_supported(u32 sleep_type)
{
	return suspend_dev && suspend_dev->system_suspend_check &&
	       suspend_dev->system_suspend_check(sleep_type);
}

int sbi_system_suspend(u32 sleep_type, ulong resume_addr, ulong opaque)
{
	int ret = SBI_ENOTSUPP;
	const struct sbi_domain *dom = sbi_domain_thishart_ptr();
	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
	void (*jump_warmboot)(void) = (void (*)(void))scratch->warmboot_addr;
	unsigned int hartid = current_hartid();
	unsigned long prev_mode;
	unsigned long i;

	if (!dom || !dom->system_suspend_allowed)
		return SBI_EFAIL;

	if (!suspend_dev || !suspend_dev->system_suspend)
		return SBI_EFAIL;

	if (!sbi_system_suspend_supported(sleep_type))
		return SBI_ENOTSUPP;

	prev_mode = (csr_read(CSR_MSTATUS) & MSTATUS_MPP) >> MSTATUS_MPP_SHIFT;
	if (prev_mode != PRV_S && prev_mode != PRV_U)
		return SBI_EFAIL;

	sbi_hartmask_for_each_hart(i, &dom->assigned_harts) {
		if (i == hartid)
			continue;
		if (__sbi_hsm_hart_get_state(i) != SBI_HSM_STATE_STOPPED)
			return SBI_EFAIL;
	}

	if (!sbi_domain_check_addr(dom, resume_addr, prev_mode,
				   SBI_DOMAIN_EXECUTE))
		return SBI_EINVALID_ADDR;

	if (!sbi_hsm_hart_change_state(scratch, SBI_HSM_STATE_STARTED,
				       SBI_HSM_STATE_SUSPENDED))
		return SBI_EFAIL;

	/* Prepare for resume */
	scratch->next_mode = prev_mode;
	scratch->next_addr = resume_addr;
	scratch->next_arg1 = opaque;

	__sbi_hsm_suspend_non_ret_save(scratch);

	/* Suspend */
	ret = suspend_dev->system_suspend(sleep_type, scratch->warmboot_addr);
	if (ret != SBI_OK) {
		if (!sbi_hsm_hart_change_state(scratch, SBI_HSM_STATE_SUSPENDED,
					       SBI_HSM_STATE_STARTED))
			sbi_hart_hang();
		return ret;
	}

	/* Resume */
	jump_warmboot();

	__builtin_unreachable();
}