master
c 272 lines 8.67 KB
Raw
1 /*
2 * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "qemu/osdep.h"
8 #include "qemu/log.h"
9 #include "qemu/main-loop.h"
10 #include "qemu/qemu-print.h"
11 #include "cpu.h"
12 #include "system/cpus.h"
13 #include "internal.h"
14 #include "exec/cpu-interrupt.h"
15 #include "cpu_helper.h"
16 #include "exec/cputlb.h"
17 #include "hex_mmu.h"
18 #include "macros.h"
19 #include "sys_macros.h"
20 #include "hw/hexagon/hexagon_tlb.h"
21 #include "hw/hexagon/hexagon_globalreg.h"
22
23 static inline void hex_log_tlbw(uint32_t index, uint64_t entry)
24 {
25 qemu_log_mask(CPU_LOG_MMU,
26 "tlbw[%03" PRIu32 "]: 0x%016" PRIx64 "\n",
27 index, entry);
28 }
29
30 void hex_tlbw(CPUHexagonState *env, uint32_t index, uint64_t value)
31 {
32 uint32_t myidx = fTLB_NONPOW2WRAP(fTLB_IDXMASK(index));
33 HexagonTLBState *tlb = env_archcpu(env)->tlb;
34 uint64_t old_entry = hexagon_tlb_read(tlb, myidx);
35
36 bool old_entry_valid = extract64(old_entry, 63, 1);
37 if (old_entry_valid && hexagon_cpu_mmu_enabled(env)) {
38 CPUState *cs = env_cpu(env);
39 tlb_flush(cs);
40 }
41 hexagon_tlb_write(tlb, myidx, value);
42 hex_log_tlbw(myidx, value);
43 }
44
45 void hex_mmu_on(CPUHexagonState *env)
46 {
47 CPUState *cs = env_cpu(env);
48 qemu_log_mask(CPU_LOG_MMU, "Hexagon MMU turned on!\n");
49 tlb_flush(cs);
50 }
51
52 void hex_mmu_off(CPUHexagonState *env)
53 {
54 CPUState *cs = env_cpu(env);
55 qemu_log_mask(CPU_LOG_MMU, "Hexagon MMU turned off!\n");
56 tlb_flush(cs);
57 }
58
59 void hex_mmu_mode_change(CPUHexagonState *env)
60 {
61 qemu_log_mask(CPU_LOG_MMU, "Hexagon mode change!\n");
62 CPUState *cs = env_cpu(env);
63 tlb_flush(cs);
64 }
65
66 bool hex_tlb_find_match(CPUHexagonState *env, uint32_t VA,
67 MMUAccessType access_type, hwaddr *PA, int *prot,
68 uint64_t *size, int32_t *excp, int mmu_idx)
69 {
70 HexagonCPU *cpu = env_archcpu(env);
71 uint32_t ssr = env->t_sreg[HEX_SREG_SSR];
72 uint8_t asid = GET_SSR_FIELD(SSR_ASID, ssr);
73 int cause_code = 0;
74
75 bool found = hexagon_tlb_find_match(cpu->tlb, asid, VA, access_type,
76 PA, prot, size, excp, &cause_code,
77 mmu_idx);
78 if (cause_code) {
79 env->cause_code = cause_code;
80 }
81 return found;
82 }
83
84 /* Called from tlbp instruction */
85 uint32_t hex_tlb_lookup(CPUHexagonState *env, uint32_t ssr, uint32_t VA)
86 {
87 HexagonCPU *cpu = env_archcpu(env);
88 uint8_t asid = GET_SSR_FIELD(SSR_ASID, ssr);
89 int cause_code = 0;
90
91 uint32_t result = hexagon_tlb_lookup(cpu->tlb, asid, VA, &cause_code);
92 if (cause_code) {
93 env->cause_code = cause_code;
94 }
95 return result;
96 }
97
98 /*
99 * Return codes:
100 * 0 or positive index of match
101 * -1 multiple matches
102 * -2 no match
103 */
104 int hex_tlb_check_overlap(CPUHexagonState *env, uint64_t entry, uint64_t index)
105 {
106 HexagonCPU *cpu = env_archcpu(env);
107 return hexagon_tlb_check_overlap(cpu->tlb, entry, index);
108 }
109
110 #ifdef CONFIG_HMP
111 void dump_mmu(MonitorHMP *hmp, CPUHexagonState *env)
112 {
113 HexagonCPU *cpu = env_archcpu(env);
114 hexagon_tlb_dump(hmp, cpu->tlb);
115 }
116 #endif
117
118 static inline void print_thread(const char *str, CPUState *cs)
119 {
120 g_assert(bql_locked());
121 CPUHexagonState *thread = cpu_env(cs);
122 bool is_stopped = cpu_is_stopped(cs);
123 int exe_mode = get_exe_mode(thread);
124 hex_lock_state_t lock_state = thread->tlb_lock_state;
125 qemu_log_mask(CPU_LOG_MMU,
126 "%s: threadId = %" PRIu32 ": %s,"
127 " exe_mode = %s, tlb_lock_state = %s\n",
128 str,
129 thread->threadId,
130 is_stopped ? "stopped" : "running",
131 exe_mode == HEX_EXE_MODE_OFF ? "off" :
132 exe_mode == HEX_EXE_MODE_RUN ? "run" :
133 exe_mode == HEX_EXE_MODE_WAIT ? "wait" :
134 exe_mode == HEX_EXE_MODE_DEBUG ? "debug" :
135 "unknown",
136 lock_state == HEX_LOCK_UNLOCKED ? "unlocked" :
137 lock_state == HEX_LOCK_WAITING ? "waiting" :
138 lock_state == HEX_LOCK_OWNER ? "owner" :
139 "unknown");
140 }
141
142 static inline void print_thread_states(const char *str)
143 {
144 CPUState *cs;
145 CPU_FOREACH(cs) {
146 print_thread(str, cs);
147 }
148 }
149
150 void hex_tlb_lock(CPUHexagonState *env)
151 {
152 qemu_log_mask(CPU_LOG_MMU, "hex_tlb_lock: " TARGET_FMT_ld "\n",
153 env->threadId);
154 BQL_LOCK_GUARD();
155 g_assert((env->tlb_lock_count == 0) || (env->tlb_lock_count == 1));
156
157 HexagonCPU *cpu = env_archcpu(env);
158 uint32_t syscfg = cpu->globalregs ?
159 hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG,
160 env->threadId) : 0;
161 uint8_t tlb_lock = GET_SYSCFG_FIELD(SYSCFG_TLBLOCK, syscfg);
162 if (tlb_lock) {
163 if (env->tlb_lock_state == HEX_LOCK_QUEUED) {
164 env->next_PC += 4;
165 env->tlb_lock_count++;
166 env->tlb_lock_state = HEX_LOCK_OWNER;
167 SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 1);
168 return;
169 }
170 if (env->tlb_lock_state == HEX_LOCK_OWNER) {
171 qemu_log_mask(CPU_LOG_MMU | LOG_GUEST_ERROR,
172 "Double tlblock at PC: 0x%" PRIx32
173 ", thread may hang\n",
174 env->next_PC);
175 env->next_PC += 4;
176 CPUState *cs = env_cpu(env);
177 cpu_interrupt(cs, CPU_INTERRUPT_HALT);
178 return;
179 }
180 env->tlb_lock_state = HEX_LOCK_WAITING;
181 CPUState *cs = env_cpu(env);
182 cpu_interrupt(cs, CPU_INTERRUPT_HALT);
183 } else {
184 env->next_PC += 4;
185 env->tlb_lock_count++;
186 env->tlb_lock_state = HEX_LOCK_OWNER;
187 SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 1);
188 }
189
190 if (qemu_loglevel_mask(CPU_LOG_MMU)) {
191 qemu_log_mask(CPU_LOG_MMU, "Threads after hex_tlb_lock:\n");
192 print_thread_states("\tThread");
193 }
194 }
195
196 void hex_tlb_unlock(CPUHexagonState *env)
197 {
198 BQL_LOCK_GUARD();
199 g_assert((env->tlb_lock_count == 0) || (env->tlb_lock_count == 1));
200
201 /* Nothing to do if the TLB isn't locked by this thread */
202 HexagonCPU *cpu = env_archcpu(env);
203 uint32_t syscfg = cpu->globalregs ?
204 hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG,
205 env->threadId) : 0;
206 uint8_t tlb_lock = GET_SYSCFG_FIELD(SYSCFG_TLBLOCK, syscfg);
207 if ((tlb_lock == 0) ||
208 (env->tlb_lock_state != HEX_LOCK_OWNER)) {
209 qemu_log_mask(LOG_GUEST_ERROR,
210 "thread %" PRIu32 " attempted to tlbunlock"
211 " without having the lock, tlb_lock state = %u\n",
212 env->threadId, (unsigned)env->tlb_lock_state);
213 g_assert(env->tlb_lock_state != HEX_LOCK_WAITING);
214 return;
215 }
216
217 env->tlb_lock_count--;
218 env->tlb_lock_state = HEX_LOCK_UNLOCKED;
219 SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 0);
220
221 /* Look for a thread to unlock */
222 unsigned int this_threadId = env->threadId;
223 CPUHexagonState *unlock_thread = NULL;
224 CPUState *cs;
225 CPU_FOREACH(cs) {
226 CPUHexagonState *thread = cpu_env(cs);
227
228 /*
229 * The hardware implements round-robin fairness, so we look for threads
230 * starting at env->threadId + 1 and incrementing modulo the number of
231 * threads.
232 *
233 * To implement this, we check if thread is a earlier in the modulo
234 * sequence than unlock_thread.
235 * if unlock thread is higher than this thread
236 * thread must be between this thread and unlock_thread
237 * else
238 * thread higher than this thread is ahead of unlock_thread
239 * thread must be lower then unlock thread
240 */
241 if (thread->tlb_lock_state == HEX_LOCK_WAITING) {
242 if (!unlock_thread) {
243 unlock_thread = thread;
244 } else if (unlock_thread->threadId > this_threadId) {
245 if (this_threadId < thread->threadId &&
246 thread->threadId < unlock_thread->threadId) {
247 unlock_thread = thread;
248 }
249 } else {
250 if (thread->threadId > this_threadId) {
251 unlock_thread = thread;
252 }
253 if (thread->threadId < unlock_thread->threadId) {
254 unlock_thread = thread;
255 }
256 }
257 }
258 }
259 if (unlock_thread) {
260 cs = env_cpu(unlock_thread);
261 print_thread("\tWaiting thread found", cs);
262 unlock_thread->tlb_lock_state = HEX_LOCK_QUEUED;
263 SET_SYSCFG_FIELD(unlock_thread, SYSCFG_TLBLOCK, 1);
264 cpu_interrupt(cs, CPU_INTERRUPT_TLB_UNLOCK);
265 }
266
267 if (qemu_loglevel_mask(CPU_LOG_MMU)) {
268 qemu_log_mask(CPU_LOG_MMU, "Threads after hex_tlb_unlock:\n");
269 print_thread_states("\tThread");
270 }
271
272 }