/* * Secondary cpu support * * Copyright 2016 Suraj Jitindar Singh, IBM. * * This work is licensed under the terms of the GNU LGPL, version 2. */ #include #include #include #include #include #include #include #include #include #include #include struct secondary_entry_data { secondary_entry_fn entry; }; int nr_cpus_online = 1; static void stop_self(int cpu_id) { if (machine_is_powernv()) { if (opal_call(OPAL_RETURN_CPU, 0, 0, 0) != OPAL_SUCCESS) { printf("OPAL_RETURN_CPU failed\n"); } } else { rtas_stop_self(); } printf("failed to stop cpu %d\n", cpu_id); assert(0); } void main_secondary(struct cpu *cpu); void main_secondary(struct cpu *cpu) { mtspr(SPR_SPRG0, (unsigned long)cpu); __current_cpu = cpu; enable_mcheck(); cpu_init_ipis(); atomic_fetch_inc(&nr_cpus_online); cpu->entry(cpu->server_no); mb(); atomic_fetch_dec(&nr_cpus_online); stop_self(cpu->server_no); } enum OpalThreadStatus { OPAL_THREAD_INACTIVE = 0x0, OPAL_THREAD_STARTED = 0x1, OPAL_THREAD_UNAVAILABLE = 0x2 /* opal-v3 */ }; #define H_EOI 0x64 #define H_CPPR 0x68 #define H_IPI 0x6c #define H_XIRR 0x74 static void (*ipi_fn)(struct pt_regs *regs, void *data); static void dbell_handler(struct pt_regs *regs, void *data) { /* sync */ ipi_fn(regs, data); } static void extint_handler(struct pt_regs *regs, void *data) { int32_t xirr; int32_t xisr; int64_t rc; asm volatile("mr r3,%1 ; sc 1 ; mr %0,r4" : "=r"(xirr) : "r"(H_XIRR)); xisr = xirr & 0xffffff; if (xisr == 2) { /* IPI */ rc = hcall(H_IPI, smp_processor_id(), 0xff); assert(rc == H_SUCCESS); } xirr |= (5 << 24); rc = hcall(H_EOI, xirr); assert(rc == H_SUCCESS); /* lower IPI */ ipi_fn(regs, data); } void cpu_init_ipis(void) { if (machine_is_powernv()) { /* skiboot can leave some messages set */ unsigned long rb = (5 << (63-36)); asm volatile("msgclr %0" :: "r"(rb) : "memory"); } } void local_ipi_enable(void) { if (machine_is_pseries()) { hcall(H_CPPR, 5); } } void local_ipi_disable(void) { if (machine_is_pseries()) { hcall(H_CPPR, 0); } } void register_ipi(void (*fn)(struct pt_regs *, void *), void *data) { ipi_fn = fn; if (machine_is_powernv()) { handle_exception(0xe80, &dbell_handler, data); } else { handle_exception(0x500, &extint_handler, data); } } void unregister_ipi(void) { if (machine_is_powernv()) { handle_exception(0xe80, NULL, NULL); } else { handle_exception(0x500, NULL, NULL); } } void send_ipi(int cpu_id) { if (machine_is_powernv()) { unsigned long rb = (5 << (63-36)) | cpu_id; asm volatile("lwsync" ::: "memory"); asm volatile("msgsnd %0" :: "r"(rb) : "memory"); } else { hcall(H_IPI, cpu_id, 4); } } static int nr_started = 1; extern void start_secondary(uint64_t server_no); /* asm entry point */ static bool cpu_is_running(int cpu_id) { if (machine_is_powernv()) { int64_t ret; uint8_t status; ret = opal_call(OPAL_QUERY_CPU_STATUS, cpu_id, (unsigned long)&status, 0); if (ret != OPAL_SUCCESS) { printf("OPAL_QUERY_CPU_STATUS failed for cpu %d\n", cpu_id); return false; } return (status != OPAL_THREAD_INACTIVE); } else { uint32_t query_token; int outputs[1], ret; ret = rtas_token("query-cpu-stopped-state", &query_token); if (ret != 0) { printf("rtas token query-cpu-stopped-state failed\n"); return false; } ret = rtas_call(query_token, 1, 2, outputs, cpu_id); if (ret) { printf("query-cpu-stopped-state failed for cpu %d\n", cpu_id); return ret; } if (outputs[0]) /* cpu not in stopped state */ return true; return false; } } /* * Start stopped thread cpu_id at entry * Returns: <0 on failure to start stopped cpu * 0 on success * >0 on cpu not in stopped state */ static int start_thread(int cpu_id, secondary_entry_fn entry) { struct cpu *cpu; uint64_t tb; if (nr_started >= NR_CPUS) { /* Reached limit */ return -1; } if (cpu_id == smp_processor_id()) { /* Boot CPU already started */ return -1; } tb = get_tb(); while (cpu_is_running(cpu_id)) { if (get_tb() - tb > 3*tb_hz) { printf("Unable to start running CPU:%d\n", cpu_id); return 1; } } cpu = &cpus[nr_started]; nr_started++; cpu_init(cpu, cpu_id); cpu->entry = entry; if (machine_is_powernv()) { if (opal_call(OPAL_START_CPU, cpu_id, (unsigned long)start_secondary, 0) != OPAL_SUCCESS) { printf("failed to start cpu %d\n", cpu_id); return -1; } } else { uint32_t start_token; int ret; ret = rtas_token("start-cpu", &start_token); assert(ret == 0); ret = rtas_call(start_token, 3, 1, NULL, cpu_id, start_secondary, cpu_id); if (ret) { printf("failed to start cpu %d\n", cpu_id); return ret; } } return 0; } /* * Start all stopped threads (vcpus) on cpu_node * Returns: Number of stopped cpus which were successfully started */ static void start_core(int cpu_node, secondary_entry_fn entry) { int len, i, nr_threads; const struct fdt_property *prop; u32 *threads; /* Get the id array of threads on this cpu_node */ prop = fdt_get_property(dt_fdt(), cpu_node, "ibm,ppc-interrupt-server#s", &len); assert(prop); nr_threads = len >> 2; /* Divide by 4 since 4 bytes per thread */ threads = (u32 *)prop->data; /* Array of valid ids */ for (i = 0; i < nr_threads; i++) start_thread(fdt32_to_cpu(threads[i]), entry); } static void start_each_secondary(int fdtnode, u64 regval __unused, void *info) { struct secondary_entry_data *datap = info; start_core(fdtnode, datap->entry); } /* * Start all stopped cpus on the guest at entry with register 3 set to r3 * We expect that we come in with only one thread currently started * Returns: TRUE on success * FALSE on failure */ bool start_all_cpus(secondary_entry_fn entry) { struct secondary_entry_data data = { entry }; uint64_t tb; int ret; assert(nr_cpus_online == 1); assert(nr_started == 1); ret = dt_for_each_cpu_node(start_each_secondary, &data); assert(ret == 0); assert(nr_started == nr_cpus_present); tb = get_tb(); while (nr_cpus_online < nr_cpus_present) { if (get_tb() - tb > 3*tb_hz) { printf("failed to start all secondaries\n"); assert(0); } cpu_relax(); } return 1; } /* * Start stopped thread cpu_id at entry * Returns: <0 on failure to start stopped cpu * 0 on success * >0 on cpu not in stopped state */ static int wait_thread(int cpu_id) { uint64_t tb; /* Skip the caller */ if (cpu_id == smp_processor_id()) { return 0; } tb = get_tb(); while (cpu_is_running(cpu_id)) { if (get_tb() - tb > 3*tb_hz) { printf("Timeout waiting to stop CPU:%d\n", cpu_id); return 1; } } return 0; } /* * Wait for running threads (vcpus) on cpu_node to stop */ static void wait_core(int cpu_node) { int len, i, nr_threads; const struct fdt_property *prop; u32 *threads; /* Get the id array of threads on this cpu_node */ prop = fdt_get_property(dt_fdt(), cpu_node, "ibm,ppc-interrupt-server#s", &len); assert(prop); nr_threads = len >> 2; /* Divide by 4 since 4 bytes per thread */ threads = (u32 *)prop->data; /* Array of valid ids */ for (i = 0; i < nr_threads; i++) wait_thread(fdt32_to_cpu(threads[i])); } static void wait_each_secondary(int fdtnode, u64 regval __unused, void *info) { wait_core(fdtnode); } void stop_all_cpus(void) { while (nr_cpus_online > 1) cpu_relax(); dt_for_each_cpu_node(wait_each_secondary, NULL); mb(); nr_started = 1; }