| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2025 Ant Group |
| * Author: Tiwei Bie <tiwei.btw@antgroup.com> |
| * |
| * Based on the previous implementation in TT mode |
| * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) |
| */ |
| |
| #include <linux/sched.h> |
| #include <linux/sched/task.h> |
| #include <linux/sched/task_stack.h> |
| #include <linux/module.h> |
| #include <linux/processor.h> |
| #include <linux/threads.h> |
| #include <linux/cpu.h> |
| #include <linux/hardirq.h> |
| #include <linux/smp.h> |
| #include <linux/smp-internal.h> |
| #include <init.h> |
| #include <kern.h> |
| #include <os.h> |
| #include <smp.h> |
| |
| enum { |
| UML_IPI_RES = 0, |
| UML_IPI_CALL_SINGLE, |
| UML_IPI_CALL, |
| UML_IPI_STOP, |
| }; |
| |
| void arch_smp_send_reschedule(int cpu) |
| { |
| os_send_ipi(cpu, UML_IPI_RES); |
| } |
| |
| void arch_send_call_function_single_ipi(int cpu) |
| { |
| os_send_ipi(cpu, UML_IPI_CALL_SINGLE); |
| } |
| |
| void arch_send_call_function_ipi_mask(const struct cpumask *mask) |
| { |
| int cpu; |
| |
| for_each_cpu(cpu, mask) |
| os_send_ipi(cpu, UML_IPI_CALL); |
| } |
| |
| void smp_send_stop(void) |
| { |
| int cpu, me = smp_processor_id(); |
| |
| for_each_online_cpu(cpu) { |
| if (cpu == me) |
| continue; |
| os_send_ipi(cpu, UML_IPI_STOP); |
| } |
| } |
| |
| static void ipi_handler(int vector, struct uml_pt_regs *regs) |
| { |
| struct pt_regs *old_regs = set_irq_regs((struct pt_regs *)regs); |
| int cpu = raw_smp_processor_id(); |
| |
| irq_enter(); |
| |
| if (current->mm) |
| os_alarm_process(current->mm->context.id.pid); |
| |
| switch (vector) { |
| case UML_IPI_RES: |
| inc_irq_stat(irq_resched_count); |
| scheduler_ipi(); |
| break; |
| |
| case UML_IPI_CALL_SINGLE: |
| inc_irq_stat(irq_call_count); |
| generic_smp_call_function_single_interrupt(); |
| break; |
| |
| case UML_IPI_CALL: |
| inc_irq_stat(irq_call_count); |
| generic_smp_call_function_interrupt(); |
| break; |
| |
| case UML_IPI_STOP: |
| set_cpu_online(cpu, false); |
| while (1) |
| pause(); |
| break; |
| |
| default: |
| pr_err("CPU#%d received unknown IPI (vector=%d)!\n", cpu, vector); |
| break; |
| } |
| |
| irq_exit(); |
| set_irq_regs(old_regs); |
| } |
| |
| void uml_ipi_handler(int vector) |
| { |
| struct uml_pt_regs r = { .is_user = 0 }; |
| |
| preempt_disable(); |
| ipi_handler(vector, &r); |
| preempt_enable(); |
| } |
| |
| /* AP states used only during CPU startup */ |
| enum { |
| UML_CPU_PAUSED = 0, |
| UML_CPU_RUNNING, |
| }; |
| |
| static int cpu_states[NR_CPUS]; |
| |
| static int start_secondary(void *unused) |
| { |
| int err, cpu = raw_smp_processor_id(); |
| |
| notify_cpu_starting(cpu); |
| set_cpu_online(cpu, true); |
| |
| err = um_setup_timer(); |
| if (err) |
| panic("CPU#%d failed to setup timer, err = %d", cpu, err); |
| |
| local_irq_enable(); |
| |
| cpu_startup_entry(CPUHP_AP_ONLINE_IDLE); |
| |
| return 0; |
| } |
| |
| void uml_start_secondary(void *opaque) |
| { |
| int cpu = raw_smp_processor_id(); |
| struct mm_struct *mm = &init_mm; |
| struct task_struct *idle; |
| |
| stack_protections((unsigned long) &cpu_irqstacks[cpu]); |
| set_sigstack(&cpu_irqstacks[cpu], THREAD_SIZE); |
| |
| set_cpu_present(cpu, true); |
| os_futex_wait(&cpu_states[cpu], UML_CPU_PAUSED); |
| |
| smp_rmb(); /* paired with smp_wmb() in __cpu_up() */ |
| |
| idle = cpu_tasks[cpu]; |
| idle->thread_info.cpu = cpu; |
| |
| mmgrab(mm); |
| idle->active_mm = mm; |
| |
| idle->thread.request.thread.proc = start_secondary; |
| idle->thread.request.thread.arg = NULL; |
| |
| new_thread(task_stack_page(idle), &idle->thread.switch_buf, |
| new_thread_handler); |
| os_start_secondary(opaque, &idle->thread.switch_buf); |
| } |
| |
| void __init smp_prepare_cpus(unsigned int max_cpus) |
| { |
| int err, cpu, me = smp_processor_id(); |
| unsigned long deadline; |
| |
| os_init_smp(); |
| |
| for_each_possible_cpu(cpu) { |
| if (cpu == me) |
| continue; |
| |
| pr_debug("Booting processor %d...\n", cpu); |
| err = os_start_cpu_thread(cpu); |
| if (err) { |
| pr_crit("CPU#%d failed to start cpu thread, err = %d", |
| cpu, err); |
| continue; |
| } |
| |
| deadline = jiffies + msecs_to_jiffies(1000); |
| spin_until_cond(cpu_present(cpu) || |
| time_is_before_jiffies(deadline)); |
| |
| if (!cpu_present(cpu)) |
| pr_crit("CPU#%d failed to boot\n", cpu); |
| } |
| } |
| |
| int __cpu_up(unsigned int cpu, struct task_struct *tidle) |
| { |
| cpu_tasks[cpu] = tidle; |
| smp_wmb(); /* paired with smp_rmb() in uml_start_secondary() */ |
| cpu_states[cpu] = UML_CPU_RUNNING; |
| os_futex_wake(&cpu_states[cpu]); |
| spin_until_cond(cpu_online(cpu)); |
| |
| return 0; |
| } |
| |
| void __init smp_cpus_done(unsigned int max_cpus) |
| { |
| } |
| |
| /* Set in uml_ncpus_setup */ |
| int uml_ncpus = 1; |
| |
| void __init prefill_possible_map(void) |
| { |
| int cpu; |
| |
| for (cpu = 0; cpu < uml_ncpus; cpu++) |
| set_cpu_possible(cpu, true); |
| for (; cpu < NR_CPUS; cpu++) |
| set_cpu_possible(cpu, false); |
| } |
| |
| static int __init uml_ncpus_setup(char *line, int *add) |
| { |
| *add = 0; |
| |
| if (kstrtoint(line, 10, ¨_ncpus)) { |
| os_warn("%s: Couldn't parse '%s'\n", __func__, line); |
| return -1; |
| } |
| |
| uml_ncpus = clamp(uml_ncpus, 1, NR_CPUS); |
| |
| return 0; |
| } |
| |
| __uml_setup("ncpus=", uml_ncpus_setup, |
| "ncpus=<# of desired CPUs>\n" |
| " This tells UML how many virtual processors to start. The maximum\n" |
| " number of supported virtual processors can be obtained by querying\n" |
| " the CONFIG_NR_CPUS option using --showconfig.\n\n" |
| ); |
| |
| EXPORT_SYMBOL(uml_curr_cpu); |